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.

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}