store.gno
5.11 Kb · 169 lines
1package app
2
3import "strconv"
4
5// kv is the schema-free key/value data itself. It is unexported and never
6// handed out: callers reach it only through a [Store], which gates every
7// operation. Keeping the data and the accessor separate is what lets the
8// accessor be a revocable capability while the data persists untouched.
9//
10// It exists because a struct's fields are as permanent as the realm that
11// declares them: if your state is `type State struct { count int64 }` in a
12// realm you can never redeploy, you can never add a second field. Keys are
13// data, so a kv can grow forever. The trade is everything a type gives you:
14// nothing is checked, values are strings, and a typo in a key is a silent
15// empty read.
16//
17// # Where the writes land
18//
19// A kv is allocated by your realm, so Gno's storage-realm borrow persists
20// every write in the realm that owns it -- not in the caller's. That is what
21// lets an implementation realm mutate your state without holding any of it.
22type kv struct {
23 keys []string // sorted
24 vals []string
25}
26
27func newKV() *kv { return &kv{} }
28
29// find returns the insertion index for k and whether k is present.
30func (s *kv) find(k string) (int, bool) {
31 lo, hi := 0, len(s.keys)
32 for lo < hi {
33 mid := int(uint(lo+hi) >> 1)
34 if s.keys[mid] < k {
35 lo = mid + 1
36 } else {
37 hi = mid
38 }
39 }
40 return lo, lo < len(s.keys) && s.keys[lo] == k
41}
42
43func (s *kv) get(k string) string {
44 if i, ok := s.find(k); ok {
45 return s.vals[i]
46 }
47 return ""
48}
49
50func (s *kv) has(k string) bool {
51 _, ok := s.find(k)
52 return ok
53}
54
55func (s *kv) set(k, v string) {
56 i, ok := s.find(k)
57 if ok {
58 s.vals[i] = v
59 return
60 }
61 s.keys = append(s.keys, "")
62 s.vals = append(s.vals, "")
63 copy(s.keys[i+1:], s.keys[i:])
64 copy(s.vals[i+1:], s.vals[i:])
65 s.keys[i], s.vals[i] = k, v
66}
67
68func (s *kv) del(k string) bool {
69 i, ok := s.find(k)
70 if !ok {
71 return false
72 }
73 s.keys = append(s.keys[:i], s.keys[i+1:]...)
74 s.vals = append(s.vals[:i], s.vals[i+1:]...)
75 return true
76}
77
78func (s *kv) length() int { return len(s.keys) }
79
80func (s *kv) allKeys() []string {
81 dup := make([]string, len(s.keys))
82 copy(dup, s.keys)
83 return dup
84}
85
86func (s *kv) keysWithPrefix(prefix string) []string {
87 out := []string{}
88 i, _ := s.find(prefix)
89 for ; i < len(s.keys); i++ {
90 k := s.keys[i]
91 if len(k) < len(prefix) || k[:len(prefix)] != prefix {
92 break
93 }
94 out = append(out, k)
95 }
96 return out
97}
98
99func (s *kv) getInt(k string) int64 {
100 n, err := strconv.ParseInt(s.get(k), 10, 64)
101 if err != nil {
102 return 0
103 }
104 return n
105}
106
107func (s *kv) setInt(k string, v int64) { s.set(k, strconv.FormatInt(v, 10)) }
108
109func (s *kv) addInt(k string, delta int64) int64 {
110 n := s.getInt(k) + delta
111 s.setInt(k, n)
112 return n
113}
114
115// Store is the handler-facing accessor. It is NOT the data -- it is a
116// permission to touch it, bound to the realm value it was issued for.
117//
118// This is what closes the capability leak. The old design handed out a raw
119// pointer to the data, which a handler could stash in a package variable and
120// keep using after it was rolled back or the realm was frozen. A Store holds
121// the caller's realm value and re-checks it on every operation, so:
122//
123// - It cannot be stashed. Assigning it to realm state persists the realm
124// value it carries, which the VM refuses ("cannot persist realm value"),
125// aborting the transaction. The capability cannot outlive the call.
126// - It cannot be replayed. Even held transiently, a Store whose frame has
127// returned fails IsCurrent() on its next use.
128//
129// So the grant lives and dies with the exact call frame that obtained it,
130// which is the only window in which the caller is genuinely the live handler
131// (or a registered extension). See [App.Store].
132type Store struct {
133 app *App
134 rlm realm
135}
136
137// Get returns the value at k, or "" if absent. An absent key and a key set to
138// "" read the same; use Has to tell them apart.
139func (s *Store) Get(k string) string { return s.app.access(0, s.rlm).get(k) }
140
141// Has reports whether k is present.
142func (s *Store) Has(k string) bool { return s.app.access(0, s.rlm).has(k) }
143
144// Set writes v at k.
145func (s *Store) Set(k, v string) { s.app.access(0, s.rlm).set(k, v) }
146
147// Delete removes k, reporting whether it was there.
148func (s *Store) Delete(k string) bool { return s.app.access(0, s.rlm).del(k) }
149
150// Len returns the number of keys.
151func (s *Store) Len() int { return s.app.access(0, s.rlm).length() }
152
153// Keys returns a copy of the keys, in order.
154func (s *Store) Keys() []string { return s.app.access(0, s.rlm).allKeys() }
155
156// KeysWithPrefix returns the keys under a prefix, in order. Prefixes are how
157// a Store holds more than one collection: "task/1", "task/2", "meta/owner".
158func (s *Store) KeysWithPrefix(prefix string) []string {
159 return s.app.access(0, s.rlm).keysWithPrefix(prefix)
160}
161
162// GetInt reads k as an int64, returning 0 if it is absent or unparsable.
163func (s *Store) GetInt(k string) int64 { return s.app.access(0, s.rlm).getInt(k) }
164
165// SetInt writes an int64 at k.
166func (s *Store) SetInt(k string, v int64) { s.app.access(0, s.rlm).setInt(k, v) }
167
168// AddInt adds delta to k and returns the new value.
169func (s *Store) AddInt(k string, delta int64) int64 { return s.app.access(0, s.rlm).addInt(k, delta) }