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