Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}