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

storage.gno

5.86 Kb · 233 lines
  1package datastore
  2
  3import (
  4	"errors"
  5	"strings"
  6
  7	"gno.land/p/moul/collection/v0"
  8	"gno.land/p/nt/bptree/v0"
  9	"gno.land/p/nt/seqid/v0"
 10)
 11
 12// NewStorage creates a new records storage.
 13func NewStorage(name string, options ...StorageOption) Storage {
 14	s := Storage{
 15		name:       name,
 16		collection: collection.New(),
 17		schema:     NewSchema(strings.Title(name)),
 18	}
 19
 20	for _, apply := range options {
 21		apply(&s)
 22	}
 23	return s
 24}
 25
 26// Storage stores a collection of records.
 27//
 28// By default it searches records by record ID but it allows
 29// using custom user defined indexes for other record fields.
 30//
 31// When a storage is created it defines a default schema that
 32// keeps track of record fields. Storage can be optionally
 33// created with a user defined schema in cases where the number
 34// of fields has to be pre-defined or when new records must have
 35// one or more fields initialized to default values.
 36type Storage struct {
 37	name       string
 38	collection *collection.Collection
 39	schema     *Schema
 40}
 41
 42// Name returns storage's name.
 43func (s Storage) Name() string {
 44	return s.name
 45}
 46
 47// Collection returns the undelying collection used by the
 48// storage to store all records.
 49func (s Storage) Collection() *collection.Collection {
 50	return s.collection
 51}
 52
 53// Schema returns the schema being used to track record fields.
 54func (s Storage) Schema() *Schema {
 55	return s.schema
 56}
 57
 58// Size returns the number of records that the storage have.
 59func (s Storage) Size() int {
 60	return s.collection.GetIndex(collection.IDIndex).Size()
 61}
 62
 63// NewRecord creates a new storage record.
 64//
 65// If a custom schema with default field values is assigned to
 66// storage it's used to assign initial default values when new
 67// records are created.
 68//
 69// Creating a new record doesn't assign an ID to it, a new ID
 70// is generated and assigned to the record when it's saved for
 71// the first time.
 72func (s Storage) NewRecord() Record {
 73	r := &record{
 74		schema:     s.schema,
 75		collection: s.collection,
 76		values:     bptree.NewBPTree32(),
 77	}
 78
 79	// Assign default record values if the schema defines them
 80	for i, name := range s.schema.Fields() {
 81		if v, found := s.schema.GetDefaultByIndex(i); found {
 82			r.Set(name, v)
 83		}
 84	}
 85	return r
 86}
 87
 88// Query returns a recordset that matches the query parameters.
 89// By default query selects records using the ID index.
 90//
 91// Example usage:
 92//
 93//	// Get 50 records starting from the one at position 100
 94//	rs, _ := storage.Query(
 95//		WithOffset(100),
 96//		WithSize(50),
 97//	)
 98//
 99//	// Iterate records to create a new slice
100//	var records []Record
101//	rs.Iterate(func (r Record) bool {
102//		records = append(records, r)
103//		return false
104//	})
105func (s Storage) Query(options ...QueryOption) (Recordset, error) {
106	// Initialize the recordset for the query
107	rs := recordset{
108		query:   defaultQuery,
109		records: s.collection.GetIndex(collection.IDIndex),
110	}
111
112	for _, apply := range options {
113		if err := apply(&rs.query); err != nil {
114			return nil, err
115		}
116	}
117
118	indexName := rs.query.IndexName()
119	if indexName != collection.IDIndex {
120		// When using a custom index get the keys to get records from the ID index
121		keys, err := s.getIndexRecordsKeys(indexName, rs.query.IndexKey())
122		if err != nil {
123			return nil, err
124		}
125
126		// Adjust the number of keys to match available query options
127		if offset := rs.query.Offset(); offset > 0 {
128			if offset > len(keys) {
129				keys = nil
130			} else {
131				keys = keys[offset:]
132			}
133		}
134
135		if size := rs.query.Size(); size > 0 && size < len(keys) {
136			keys = keys[:size]
137		}
138
139		rs.keys = keys
140		rs.size = len(keys)
141	} else {
142		// When using the default ID index init size with the total number of records
143		rs.size = rs.records.Size()
144
145		// Adjust recordset size to match available query options
146		if offset := rs.query.Offset(); offset > 0 {
147			if offset > rs.size {
148				rs.size = 0
149			} else {
150				rs.size -= offset
151			}
152		}
153
154		if size := rs.query.Size(); size > 0 && size < rs.size {
155			rs.size = size
156		}
157	}
158
159	return rs, nil
160}
161
162// MustQuery returns a recordset that matches the query parameters or panics on error.
163// By default query selects records using the ID index.
164//
165// Example usage:
166//
167//	// Get 50 records starting from the one at position 100
168//	var records []Record
169//	storage.MustQuery(
170//		WithOffset(100),
171//		WithSize(50),
172//	).Iterate(func (r Record) bool {
173//		records = append(records, r)
174//		return false
175//	})
176func (s Storage) MustQuery(options ...QueryOption) Recordset {
177	rs, err := s.Query(options...)
178	if err != nil {
179		panic(err)
180	}
181	return rs
182}
183
184// Get returns the first record found for a key within a storage index.
185//
186// This is a convenience method to get a single record. A multi index will
187// always return the first record value for the specified key in this case.
188// To get multiple records create a query using a custom index and key value
189// or use the underlying storage collection.
190func (s Storage) Get(indexName, indexKey string) (_ Record, found bool) {
191	iter := s.collection.Get(indexName, indexKey)
192	if iter.Next() {
193		return iter.Value().Obj.(Record), true
194	}
195	return nil, false
196}
197
198// GetByID returns a record whose ID matches the specified ID.
199func (s Storage) GetByID(id uint64) (_ Record, found bool) {
200	iter := s.collection.Get(collection.IDIndex, seqid.ID(id).String())
201	if iter.Next() {
202		return iter.Value().Obj.(Record), true
203	}
204	return nil, false
205}
206
207// Delete deletes a record from the storage.
208func (s Storage) Delete(id uint64) bool {
209	return s.collection.Delete(id)
210}
211
212func (s Storage) getIndexRecordsKeys(indexName, indexKey string) ([]string, error) {
213	idx := s.collection.GetIndex(indexName)
214	if idx == nil {
215		return nil, errors.New("storage index for query not found: " + indexName)
216	}
217
218	keys := castIfaceToRecordKeys(idx.Get(indexKey))
219	if keys == nil {
220		return nil, errors.New("unexpected storage index key format")
221	}
222	return keys, nil
223}
224
225func castIfaceToRecordKeys(v interface{}) []string {
226	switch k := v.(type) {
227	case []string:
228		return k
229	case string:
230		return []string{k}
231	}
232	return nil
233}