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

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}