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

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) }