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}