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

8.84 Kb · 338 lines
  1// Package store holds GnoRadio's string codecs: records (fields behind a
  2// length header, read and changed without decoding), packed id lists (9
  3// bytes per id, sorted, JSON-ready), base-64 fixed-width numbers, the 8-digit
  4// keys and page rows of the data realm, and the Ops builder of data.Batch.
  5// It keeps no state: every realm stores these strings in the data realm.
  6package store
  7
  8import "strconv"
  9
 10// ---- Records: fields behind a length header, sliced without scanning ----
 11
 12// In the VM every byte a loop touches costs gas (strings.Split of a 200-byte
 13// record is about 2M gas). A record starts with the field count and each
 14// field's length (base-64 digits), so reading a field is a few slices.
 15
 16// Digits are the base-64 digits of record headers and of Num and Fixed,
 17// ordered so that fixed-width numbers sort as their values do.
 18const Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"
 19
 20const digits = Digits
 21
 22// dec decodes record headers, dec64 the numbers of Num: one table each
 23// spares a conversion per digit.
 24var (
 25	dec   [128]int
 26	dec64 [128]int64
 27)
 28
 29func init() {
 30	for i := 0; i < len(digits); i++ {
 31		dec[digits[i]], dec64[digits[i]] = i, int64(i)
 32	}
 33}
 34
 35// Num reads the w-digit base-64 number at position at of s (one expression
 36// for the usual widths: every VM step costs gas).
 37func Num(s string, at, w int) int64 {
 38	switch w {
 39	case 1:
 40		return dec64[s[at]]
 41	case 2:
 42		return dec64[s[at]]<<6 | dec64[s[at+1]]
 43	case 4:
 44		return dec64[s[at]]<<18 | dec64[s[at+1]]<<12 | dec64[s[at+2]]<<6 | dec64[s[at+3]]
 45	case 6:
 46		return dec64[s[at]]<<30 | dec64[s[at+1]]<<24 | dec64[s[at+2]]<<18 | dec64[s[at+3]]<<12 | dec64[s[at+4]]<<6 | dec64[s[at+5]]
 47	}
 48	var v int64
 49	for i := at; i < at+w; i++ {
 50		v = v<<6 | dec64[s[i]]
 51	}
 52	return v
 53}
 54
 55// Fixed writes v in w base-64 digits.
 56func Fixed(v int64, w int) string { return string(AppendFixed(make([]byte, 0, w), v, w)) }
 57
 58// AppendFixed appends v in w base-64 digits to b (one append for the usual
 59// widths). It panics when v does not fit.
 60func AppendFixed(b []byte, v int64, w int) []byte {
 61	if v < 0 || v>>(6*w) != 0 {
 62		panic("store: number out of range")
 63	}
 64	switch w {
 65	case 1:
 66		return append(b, digits[v])
 67	case 2:
 68		return append(b, digits[v>>6], digits[v&63])
 69	case 4:
 70		return append(b, digits[v>>18], digits[v>>12&63], digits[v>>6&63], digits[v&63])
 71	case 6:
 72		return append(b, digits[v>>30], digits[v>>24&63], digits[v>>18&63], digits[v>>12&63], digits[v>>6&63], digits[v&63])
 73	}
 74	for i := w - 1; i >= 0; i-- {
 75		b = append(b, digits[v>>(6*i)&63])
 76	}
 77	return b
 78}
 79
 80// MaxField is the longest field a record holds (3 base-64 digits).
 81const MaxField = 1<<18 - 1
 82
 83// Rec packs fields into a record (at most 63 fields).
 84func Rec(fs ...string) string {
 85	if len(fs) > 63 {
 86		panic("store: too many fields")
 87	}
 88	h := make([]byte, 1, 1+3*len(fs))
 89	h[0] = digits[len(fs)]
 90	body := make([]byte, 0, 64)
 91	for _, f := range fs {
 92		n := len(f)
 93		if n > MaxField {
 94			panic("store: field too long")
 95		}
 96		h = append(h, digits[n>>12&63], digits[n>>6&63], digits[n&63])
 97		body = append(body, f...)
 98	}
 99	return string(h) + string(body)
100}
101
102func fieldLen(rec string, i int) int {
103	return dec[rec[1+3*i]]<<12 | dec[rec[2+3*i]]<<6 | dec[rec[3+3*i]]
104}
105
106// Fields unpacks a record.
107func Fields(rec string) []string {
108	n := dec[rec[0]]
109	out := make([]string, n)
110	p := 1 + 3*n
111	for i := 0; i < n; i++ {
112		l := fieldLen(rec, i)
113		out[i] = rec[p : p+l]
114		p += l
115	}
116	return out
117}
118
119// Field returns field i of a record ("" past the last field).
120func Field(rec string, i int) string {
121	n := dec[rec[0]]
122	if i >= n {
123		return ""
124	}
125	p := 1 + 3*n
126	for k := 0; k < i; k++ {
127		p += fieldLen(rec, k)
128	}
129	return rec[p : p+fieldLen(rec, i)]
130}
131
132// With returns rec with field i replaced by v, without decoding the record.
133func With(rec string, i int, v string) string {
134	n := dec[rec[0]]
135	if i < 0 || i >= n || len(v) > MaxField {
136		panic("store: bad field")
137	}
138	p := 1 + 3*n
139	for k := 0; k < i; k++ {
140		p += fieldLen(rec, k)
141	}
142	l := len(v)
143	h := string([]byte{digits[l>>12&63], digits[l>>6&63], digits[l&63]})
144	return rec[:1+3*i] + h + rec[4+3*i:p] + v + rec[p+fieldLen(rec, i):]
145}
146
147// Append returns rec with v added as its last field, without decoding the
148// record ("" is a record of no fields).
149func Append(rec, v string) string {
150	if rec == "" {
151		return Rec(v)
152	}
153	n := dec[rec[0]]
154	if n >= 63 || len(v) > MaxField {
155		panic("store: bad field")
156	}
157	l := len(v)
158	h := string([]byte{digits[n+1], digits[l>>12&63], digits[l>>6&63], digits[l&63]})
159	return h[:1] + rec[1:1+3*n] + h[1:] + rec[1+3*n:] + v
160}
161
162// ---- Id lists: W bytes per id ("     123,"), sortable and JSON-ready ----
163
164// W is the width of one id in a list (ids up to 99,999,999). The padding is
165// spaces and the separator a comma, so "[" + list without its last comma +
166// "]" is already a JSON array: exporting a list costs no per-id work.
167const W = 9
168
169// Enc writes one id of a list.
170func Enc(id int) string {
171	if id < 0 || id > 99999999 {
172		panic("store: id out of range")
173	}
174	s := strconv.Itoa(id)
175	return "        "[len(s):] + s + ","
176}
177
178// At returns the i-th id of a list.
179func At(list string, i int) int {
180	n := 0
181	for k := i * W; k < i*W+W-1; k++ {
182		if c := list[k]; c != ' ' {
183			n = n*10 + int(c-'0')
184		}
185	}
186	return n
187}
188
189// Count is the number of ids in a list.
190func Count(list string) int { return len(list) / W }
191
192// JSON returns a list as a JSON array.
193func JSON(list string) string {
194	if list == "" {
195		return "[]"
196	}
197	return "[" + list[:len(list)-1] + "]"
198}
199
200// Ints decodes a list.
201func Ints(list string) []int {
202	out := make([]int, 0, Count(list))
203	for i := 0; i < Count(list); i++ {
204		out = append(out, At(list, i))
205	}
206	return out
207}
208
209// Pack encodes ids in their order.
210func Pack(ids []int) string {
211	b := make([]byte, 0, W*len(ids))
212	for _, id := range ids {
213		b = append(b, Enc(id)...)
214	}
215	return string(b)
216}
217
218// Page returns up to limit ids from offset, from the end when desc.
219func Page(list string, offset, limit int, desc bool) []int {
220	n := Count(list)
221	var out []int
222	for k := offset; k >= 0 && k < n && len(out) < limit; k++ {
223		i := k
224		if desc {
225			i = n - 1 - k
226		}
227		out = append(out, At(list, i))
228	}
229	return out
230}
231
232// find returns the position of id in a sorted list, or where it goes.
233func find(list string, id int) (int, bool) {
234	lo, hi := 0, Count(list)
235	for lo < hi {
236		mid := (lo + hi) / 2
237		if At(list, mid) < id {
238			lo = mid + 1
239		} else {
240			hi = mid
241		}
242	}
243	return lo, lo < Count(list) && At(list, lo) == id
244}
245
246// Add inserts id into a sorted list; false if already there.
247func Add(sorted string, id int) (string, bool) {
248	i, ok := find(sorted, id)
249	if ok {
250		return sorted, false
251	}
252	return sorted[:i*W] + Enc(id) + sorted[i*W:], true
253}
254
255// Remove deletes id from a sorted list; false if absent.
256func Remove(sorted string, id int) (string, bool) {
257	i, ok := find(sorted, id)
258	if !ok {
259		return sorted, false
260	}
261	return sorted[:i*W] + sorted[i*W+W:], true
262}
263
264// ---- Keys, page rows and batch ops for the data realm ----
265
266// Pad writes a number as an 8-digit key, so keys sort as numbers do.
267func Pad(n int) string {
268	if n < 0 || n > 99999999 {
269		panic("store: key number out of range")
270	}
271	s := strconv.Itoa(n)
272	return "00000000"[len(s):] + s
273}
274
275// TimeKey writes a unix time as a 12-digit key, so keys sort as times do.
276func TimeKey(t int64) string {
277	s := strconv.FormatInt(t, 10)
278	return "000000000000"[len(s):] + s
279}
280
281// Slot is where record id (from 1) of a chunked collection lives: the key
282// of its chunk of size records and its index there.
283func Slot(id, size int) (string, int) { return Pad((id - 1) / size), (id - 1) % size }
284
285// Limit is a page size: n, or max when n is out of 1..max.
286func Limit(n, max int) int {
287	if n < 1 || n > max {
288		return max
289	}
290	return n
291}
292
293// Row encodes one key and value of a page: "len:key" + "len:value".
294func Row(k, v string) string {
295	return strconv.Itoa(len(k)) + ":" + k + strconv.Itoa(len(v)) + ":" + v
296}
297
298// Rows decodes a page of Row strings into its keys and values.
299func Rows(page string) (keys, values []string) {
300	for page != "" {
301		var k, v string
302		k, page = cut(page)
303		v, page = cut(page)
304		keys, values = append(keys, k), append(values, v)
305	}
306	return keys, values
307}
308
309// cut reads one "len:bytes" item off the front of s.
310func cut(s string) (item, rest string) {
311	i, n := 0, 0
312	for i < len(s) && i < 6 && s[i] >= '0' && s[i] <= '9' {
313		n = n*10 + int(s[i]-'0') // the length, read as it is scanned
314		i++
315	}
316	if i == 0 || i == len(s) || s[i] != ':' {
317		panic("store: bad row")
318	}
319	if n > len(s)-i-1 {
320		panic("store: bad row")
321	}
322	return s[i+1 : i+1+n], s[i+1+n:]
323}
324
325// Batch op names: an op is four strings (name, collection, key, value).
326const (
327	OpSet = "set"
328	OpDel = "del"
329)
330
331// Ops builds the argument of data.Batch: ops = ops.Set(c, k, v).
332type Ops []string
333
334// Set adds a write of v at key k of collection c.
335func (o Ops) Set(c, k, v string) Ops { return append(o, OpSet, c, k, v) }
336
337// Del adds a removal of key k of collection c.
338func (o Ops) Del(c, k string) Ops { return append(o, OpDel, c, k, "") }