Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

lazy.gno

2.94 Kb · 108 lines
  1// untrusted-render: records are seeded in init or derived from lazy/v0's own
  2// init; this realm exposes no setter, so no caller string ever reaches Render.
  3//
  4// Package lazy is version 1 of the "lazy migration" upgrade pattern
  5// (pattern D of the exploration; see ../README.md).
  6//
  7// v1 changes the stored shape (Score widens, Active is new) and migrates one
  8// record at a time, on first touch, paid for by whoever touched it. There is
  9// no migration transaction and no pause.
 10//
 11// The catch on today's gno: migrating on read MUTATES, so the read path has to
 12// be a crossing function. Get therefore costs a transaction and is invisible to
 13// vm/qeval and to Render. Peek is the honest read-only half, and it reports the
 14// pre-migration answer for records that have not been touched yet.
 15package lazy
 16
 17import (
 18	v0 "gno.land/r/moul/x/upgrade/lazy/v0"
 19
 20	"gno.land/p/nt/avl/v0"
 21	"gno.land/p/nt/ufmt/v0"
 22)
 23
 24// Record is v1's shape: Score widened to int64 and scaled by 1000, plus a new
 25// Active flag that migrated records get set to true.
 26type Record struct {
 27	Name   string
 28	Score  int64
 29	Active bool
 30}
 31
 32func (r *Record) String() string {
 33	if r == nil {
 34		return "nil"
 35	}
 36	return ufmt.Sprintf("v1{%s %d %t}", r.Name, r.Score, r.Active)
 37}
 38
 39var records = avl.NewTree()
 40
 41func init() {
 42	records.Set("cyd", &Record{Name: "cyd", Score: 3000, Active: true})
 43}
 44
 45// migrate converts a v0 record into a v1 one. Kept separate from Get so the
 46// conversion is testable without a write.
 47func migrate(old *v0.Record) *Record {
 48	return &Record{
 49		Name:   old.Name + "-migrated",
 50		Score:  int64(old.Score) * 1000,
 51		Active: true,
 52	}
 53}
 54
 55// Get returns a record, migrating it from v0 on first touch.
 56//
 57// Crossing, because the migration writes. That is the cost of the pattern: the
 58// cheapest possible read is now a transaction.
 59func Get(cur realm, key string) *Record {
 60	if v := records.Get(key); v != nil {
 61		return v.(*Record)
 62	}
 63	old := v0.Get(key)
 64	if old == nil {
 65		return nil
 66	}
 67	r := migrate(old)
 68	records.Set(key, r)
 69	return r
 70}
 71
 72// Peek is the read-only half: it never migrates, so a record that has not been
 73// touched yet reads as its v0 self converted on the fly and NOT stored.
 74func Peek(key string) *Record {
 75	if v := records.Get(key); v != nil {
 76		return v.(*Record)
 77	}
 78	if old := v0.Get(key); old != nil {
 79		return migrate(old)
 80	}
 81	return nil
 82}
 83
 84// Migrated is how many records have actually moved over.
 85func Migrated() int {
 86	return records.Size()
 87}
 88
 89// Pending is how many v0 records have never been touched. It is only a bound:
 90// v0 keeps every record it ever had, migrated or not.
 91func Pending() int {
 92	n := 0
 93	for _, k := range []string{"ada", "bob"} {
 94		if records.Get(k) == nil && v0.Get(k) != nil {
 95			n++
 96		}
 97	}
 98	return n
 99}
100
101func Render(_ string) string {
102	out := ufmt.Sprintf("lazy/v1: %d migrated, %d pending in v0\n", Migrated(), Pending())
103	records.Iterate("", "", func(k string, v any) bool {
104		out += ufmt.Sprintf("- %s: %s\n", k, v.(*Record).String())
105		return false
106	})
107	return out
108}