record.gno
5.43 Kb · 232 lines
1package datastore
2
3import (
4 "errors"
5
6 "gno.land/p/moul/collection/v0"
7 "gno.land/p/nt/bptree/v0"
8 "gno.land/p/nt/seqid/v0"
9)
10
11// ErrUndefinedField indicates that a field in not defined in a record's schema.
12var ErrUndefinedField = errors.New("undefined field")
13
14type (
15 // Record stores values for one or more fields.
16 Record interface {
17 ReadOnlyRecord
18
19 // Set assings a value to a record field.
20 // If the field doesn't exist it's created if the underlying schema allows it.
21 // Storage schema can optionally be strict in which case no new fields other than
22 // the ones that were previously defined are allowed.
23 Set(field string, value interface{}) error
24
25 // Save assigns an ID to newly created records and update storage indexes.
26 Save() bool
27 }
28
29 // ReadOnlyRecord defines an interface for read-only records.
30 ReadOnlyRecord interface {
31 // ID returns record's ID
32 ID() uint64
33
34 // Key returns a string representation of the record's ID.
35 // It's used to be able to search records within the ID index.
36 Key() string
37
38 // Type returns the record's type.
39 Type() string
40
41 // Fields returns the list of the record's field names.
42 Fields() []string
43
44 // IsEmpty checks if the record has no values.
45 IsEmpty() bool
46
47 // HasField checks if the record has a specific field.
48 HasField(name string) bool
49
50 // Get returns the value of a record's field.
51 Get(field string) (value interface{}, found bool)
52
53 // MustGet returns the value of a record's field or panics when the field is not found.
54 MustGet(field string) interface{}
55 }
56
57 // RecordIterFn defines a type for record iteration functions.
58 RecordIterFn func(Record) (stop bool)
59
60 // Recordset defines an interface that allows iterating multiple records.
61 Recordset interface {
62 // Iterate iterates records in order.
63 Iterate(fn RecordIterFn) (stopped bool)
64
65 // ReverseIterate iterates records in reverse order.
66 ReverseIterate(fn RecordIterFn) (stopped bool)
67
68 // Size returns the number of records in the recordset.
69 Size() int
70 }
71)
72
73type record struct {
74 id uint64
75 schema *Schema
76 collection *collection.Collection
77 values *bptree.BPTree // string(field index) -> interface{}
78}
79
80// ID returns record's ID
81func (r record) ID() uint64 {
82 return r.id
83}
84
85// Key returns a string representation of the record's ID.
86// It's used to be able to search records within the ID index.
87func (r record) Key() string {
88 return seqid.ID(r.id).String()
89}
90
91// Type returns the record's type.
92func (r record) Type() string {
93 return r.schema.Name()
94}
95
96// Fields returns the list of the record's field names.
97func (r record) Fields() []string {
98 return r.schema.Fields()
99}
100
101// IsEmpty checks if the record has no values.
102func (r record) IsEmpty() bool {
103 return r.values.Size() == 0
104}
105
106// HasField checks if the record has a specific field.
107func (r record) HasField(name string) bool {
108 return r.schema.HasField(name)
109}
110
111// Set assings a value to a record field.
112// If the field doesn't exist it's created if the underlying schema allows it.
113// Storage schema can optionally be strict in which case no new fields other than
114// the ones that were previously defined are allowed.
115func (r *record) Set(field string, value interface{}) error {
116 i := r.schema.GetFieldIndex(field)
117 if i == -1 {
118 if r.schema.IsStrict() {
119 return ErrUndefinedField
120 }
121
122 i, _ = r.schema.AddField(field, nil)
123 }
124
125 key := castIntToKey(i)
126 r.values.Set(key, value)
127 return nil
128}
129
130// Get returns the value of a record's field.
131func (r record) Get(field string) (value interface{}, found bool) {
132 i := r.schema.GetFieldIndex(field)
133 if i == -1 {
134 return nil, false
135 }
136
137 key := castIntToKey(i)
138 if r.values.Has(key) {
139 return r.values.Get(key), true
140 }
141 return nil, false
142}
143
144// MustGet returns the value of a record's field or panics when the field is not found.
145func (r record) MustGet(field string) interface{} {
146 v, found := r.Get(field)
147 if !found {
148 panic("field not found: " + field)
149 }
150 return v
151}
152
153// Save assigns an ID to newly created records and update storage indexes.
154func (r *record) Save() bool {
155 if r.id == 0 {
156 r.id = r.collection.Set(r)
157 return r.id != 0
158 }
159 return r.collection.Update(r.id, r)
160}
161
162type recordset struct {
163 query Query
164 records bptree.ITree
165 keys []string
166 size int
167}
168
169// Iterate iterates records in order.
170func (rs recordset) Iterate(fn RecordIterFn) (stopped bool) {
171 if rs.isUsingCustomIndex() {
172 for _, k := range rs.keys {
173 r, ok := rs.records.Get(k).(Record)
174 if !ok {
175 continue
176 }
177
178 if fn(r) {
179 return true
180 }
181 }
182
183 return false
184 }
185
186 offset := rs.query.Offset()
187 count := rs.query.Size()
188 if count == 0 {
189 count = rs.records.Size()
190 }
191
192 return rs.records.IterateByOffset(offset, count, func(_ string, v interface{}) bool {
193 return fn(v.(Record))
194 })
195}
196
197// ReverseIterate iterates records in reverse order.
198func (rs recordset) ReverseIterate(fn RecordIterFn) (stopped bool) {
199 if rs.isUsingCustomIndex() {
200 for i := len(rs.keys) - 1; i >= 0; i-- {
201 r, ok := rs.records.Get(rs.keys[i]).(Record)
202 if !ok {
203 continue
204 }
205
206 if fn(r) {
207 return true
208 }
209 }
210
211 return false
212 }
213
214 offset := rs.query.Offset()
215 count := rs.query.Size()
216 if count == 0 {
217 count = rs.records.Size()
218 }
219
220 return rs.records.ReverseIterateByOffset(offset, count, func(_ string, v interface{}) bool {
221 return fn(v.(Record))
222 })
223}
224
225// Size returns the number of records in the recordset.
226func (rs recordset) Size() int {
227 return rs.size
228}
229
230func (rs recordset) isUsingCustomIndex() bool {
231 return rs.query.IndexName() != collection.IDIndex
232}