Search Apps Documentation Source Content File Folder Download Copy Actions Download 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}