record.gno

5.38 Kb ยท 229 lines
  1package datastore
  2
  3import (
  4	"errors"
  5
  6	"gno.land/p/demo/avl"
  7	"gno.land/p/demo/seqid"
  8	"gno.land/p/moul/collection"
  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     avl.Tree // 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	return r.values.Get(key)
139}
140
141// MustGet returns the value of a record's field or panics when the field is not found.
142func (r record) MustGet(field string) interface{} {
143	v, found := r.Get(field)
144	if !found {
145		panic("field not found: " + field)
146	}
147	return v
148}
149
150// Save assigns an ID to newly created records and update storage indexes.
151func (r *record) Save() bool {
152	if r.id == 0 {
153		r.id = r.collection.Set(r)
154		return r.id != 0
155	}
156	return r.collection.Update(r.id, r)
157}
158
159type recordset struct {
160	query   Query
161	records avl.ITree
162	keys    []string
163	size    int
164}
165
166// Iterate iterates records in order.
167func (rs recordset) Iterate(fn RecordIterFn) (stopped bool) {
168	if rs.isUsingCustomIndex() {
169		for _, k := range rs.keys {
170			v, found := rs.records.Get(k)
171			if !found {
172				continue
173			}
174
175			if fn(v.(Record)) {
176				return true
177			}
178		}
179
180		return false
181	}
182
183	offset := rs.query.Offset()
184	count := rs.query.Size()
185	if count == 0 {
186		count = rs.records.Size()
187	}
188
189	return rs.records.IterateByOffset(offset, count, func(_ string, v interface{}) bool {
190		return fn(v.(Record))
191	})
192}
193
194// ReverseIterate iterates records in reverse order.
195func (rs recordset) ReverseIterate(fn RecordIterFn) (stopped bool) {
196	if rs.isUsingCustomIndex() {
197		for i := len(rs.keys) - 1; i >= 0; i-- {
198			v, found := rs.records.Get(rs.keys[i])
199			if !found {
200				continue
201			}
202
203			if fn(v.(Record)) {
204				return true
205			}
206		}
207
208		return false
209	}
210
211	offset := rs.query.Offset()
212	count := rs.query.Size()
213	if count == 0 {
214		count = rs.records.Size()
215	}
216
217	return rs.records.ReverseIterateByOffset(offset, count, func(_ string, v interface{}) bool {
218		return fn(v.(Record))
219	})
220}
221
222// Size returns the number of records in the recordset.
223func (rs recordset) Size() int {
224	return rs.size
225}
226
227func (rs recordset) isUsingCustomIndex() bool {
228	return rs.query.IndexName() != collection.IDIndex
229}