-
Notifications
You must be signed in to change notification settings - Fork 76
Expand file tree
/
Copy pathselect.go
More file actions
289 lines (250 loc) · 7.63 KB
/
Copy pathselect.go
File metadata and controls
289 lines (250 loc) · 7.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
// Copyright 2022 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package request
import (
"encoding/json"
"slices"
"github.com/sourcenetwork/immutable"
)
// Select is a complex Field with strong typing.
// It is used for sub-types in a request.
// Includes fields, and request arguments like filters, limits, etc.
type Select struct {
Field
ChildSelect
Limitable
Offsetable
Orderable
Filterable
DocIDsFilter
CIDFilter
Groupable
// ShowDeleted will return deleted documents along with non-deleted ones
// if set to true.
ShowDeleted bool
// IsEncrypted indicates that this is an encrypted query that should
// use searchable encryption to query remote nodes.
IsEncrypted bool
// targetCollectionID is the resolved root CollectionID for this select's
// target collection. It is set by the subscription event loop via
// SetTargetCollectionID at subscribe-time and consulted via
// CheckCollectionFilter to drop events from other collections without
// opening a transaction. Outside the subscription path this remains "".
targetCollectionID string
}
// SetTargetCollectionID records the resolved root CollectionID this select
// targets, so subsequent CheckCollectionFilter calls can reject events from
// other collections. Intended only for the subscription handler.
func (s *Select) SetTargetCollectionID(id string) {
s.targetCollectionID = id
}
// ChildSelect represents a type with selectable child properties.
//
// At least one child must be selected.
type ChildSelect struct {
// Fields contains the set of child properties to return.
//
// At least one child property must be selected.
Fields []Selection
}
// Validate validates the Select.
func (s *Select) Validate() []error {
result := []error{}
result = append(result, s.validateShallow()...)
for _, childSelection := range s.Fields {
switch typedChildSelection := childSelection.(type) {
case *Select:
result = append(result, typedChildSelection.validateShallow()...)
default:
// Do nothing
}
}
return result
}
func (s *Select) validateShallow() []error {
result := []error{}
result = append(result, s.validateGroupBy()...)
return result
}
func (s *Select) validateGroupBy() []error {
result := []error{}
if !s.GroupBy.HasValue() {
return result
}
for _, childSelection := range s.Fields {
switch typedChildSelection := childSelection.(type) {
case *Field:
if typedChildSelection.Name == TypeNameFieldName {
// _typeName is permitted
continue
}
var fieldExistsInGroupBy bool
var isAliasFieldInGroupBy bool
for _, groupByField := range s.GroupBy.Value().Fields {
if typedChildSelection.Name == groupByField {
fieldExistsInGroupBy = true
break
} else if typedChildSelection.Name == ToFieldID(groupByField) {
isAliasFieldInGroupBy = true
break
}
}
if !fieldExistsInGroupBy && !isAliasFieldInGroupBy {
result = append(result, NewErrSelectOfNonGroupField(typedChildSelection.Name))
}
default:
// Do nothing
}
}
return result
}
func (s *Select) ToSubscriptionSelect(docID, cid string) Selection {
var docIDFilter DocIDsFilter
// We only redefine the docID if it hasn't been defined by the user.
if !s.DocIDsFilter.DocIDs.HasValue() {
docIDFilter = DocIDsFilter{
DocIDs: immutable.Some([]string{docID}),
}
} else {
docIDFilter = s.DocIDsFilter
}
return &Select{
Field: s.Field,
ChildSelect: s.ChildSelect,
Limitable: s.Limitable,
Offsetable: s.Offsetable,
Orderable: s.Orderable,
Filterable: s.Filterable,
DocIDsFilter: docIDFilter,
CIDFilter: CIDFilter{
immutable.Some([]string{cid}),
},
Groupable: s.Groupable,
ShowDeleted: s.ShowDeleted,
}
}
// CheckCIDFilter checks if the given cid passes the CID filter.
// Returns true if the cid passes the filter, false otherwise.
// If no CID filter is set, it always passes.
func (s *Select) CheckCIDFilter(cid string) bool {
return !s.CIDs.HasValue() || slices.Contains(s.CIDs.Value(), cid)
}
// CheckCollectionFilter checks if the given root CollectionID matches the
// select's resolved target collection. Returns true if the IDs match, or if
// no target has been recorded (preserving current behaviour for callers
// outside the subscription path).
func (s *Select) CheckCollectionFilter(collectionID string) bool {
return s.targetCollectionID == "" || s.targetCollectionID == collectionID
}
// CheckDocIDFilter checks if the given docID passes the DocID filter.
// Returns true if the docID passes the filter, false otherwise.
// If no DocID filter is set, it always passes.
func (s *Select) CheckDocIDFilter(docID string) bool {
if s.DocIDs.HasValue() {
for _, id := range s.DocIDs.Value() {
if id == docID {
return true
}
}
return false
}
return true
}
// selectJson is a private object used for handling json deserialization
// of [Select] objects.
//
// It contains everything minus the [ChildSelect], which uses a custom UnmarshalJSON
// and is skipped over when embedding due to the way the std lib json pkg works.
type selectJson struct {
Field
Limitable
Offsetable
Orderable
Filterable
DocIDsFilter
CIDFilter
Groupable
ShowDeleted bool
}
func (s *Select) UnmarshalJSON(bytes []byte) error {
var selectMap selectJson
err := json.Unmarshal(bytes, &selectMap)
if err != nil {
return err
}
s.Field = selectMap.Field
s.DocIDs = selectMap.DocIDs
s.CIDs = selectMap.CIDs
s.Limitable = selectMap.Limitable
s.Offsetable = selectMap.Offsetable
s.Orderable = selectMap.Orderable
s.Groupable = selectMap.Groupable
s.Filterable = selectMap.Filterable
s.ShowDeleted = selectMap.ShowDeleted
var childSelect ChildSelect
err = json.Unmarshal(bytes, &childSelect)
if err != nil {
return err
}
s.ChildSelect = childSelect
return nil
}
// childSelectJson is a private object used for handling json deserialization
// of [ChildSelect] objects.
type childSelectJson struct {
Fields []map[string]json.RawMessage
}
func (s *ChildSelect) UnmarshalJSON(bytes []byte) error {
var selectMap childSelectJson
err := json.Unmarshal(bytes, &selectMap)
if err != nil {
return err
}
s.Fields = make([]Selection, len(selectMap.Fields))
for i, field := range selectMap.Fields {
fieldJson, err := json.Marshal(field)
if err != nil {
return err
}
var fieldValue Selection
// We detect which concrete type each `Selection` object is by detecting
// non-nillable fields, if the key is present it must be of that type.
// They must be non-nillable as nil values may have their keys omitted from
// the json. This also relies on the fields being unique. We may wish to change
// this later to custom-serialize with a `_type` property.
if _, ok := field["Fields"]; ok {
// This must be a Select, as only the `Select` type has a `Fields` field
var fieldSelect Select
err := json.Unmarshal(fieldJson, &fieldSelect)
if err != nil {
return err
}
fieldValue = &fieldSelect
} else if _, ok := field["Targets"]; ok {
// This must be an Aggregate, as only the `Aggregate` type has a `Targets` field
var fieldAggregate Aggregate
err := json.Unmarshal(fieldJson, &fieldAggregate)
if err != nil {
return err
}
fieldValue = &fieldAggregate
} else {
// This must be a Field
var fieldField Field
err := json.Unmarshal(fieldJson, &fieldField)
if err != nil {
return err
}
fieldValue = &fieldField
}
s.Fields[i] = fieldValue
}
return nil
}