-
Notifications
You must be signed in to change notification settings - Fork 5
/
index.go
385 lines (310 loc) · 10.3 KB
/
index.go
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
package tormenta
import (
"bytes"
"encoding/binary"
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/dgraph-io/badger"
"github.com/jpincas/gouuidv6"
)
// Index format
// i:indexname:root:indexcontent:entityID
// i:fullStruct:customer:5:324ds-3werwf-234wef-23wef
func index(txn *badger.Txn, entity Record) error {
keys := indexStruct(
recordValue(entity),
entity,
KeyRoot(entity),
entity.GetID(),
nil,
)
for i := range keys {
if err := txn.Set(keys[i], []byte{}); err != nil {
return err
}
}
return nil
}
func deIndex(txn *badger.Txn, entity Record) error {
keys := indexStruct(
recordValue(entity),
entity,
KeyRoot(entity),
entity.GetID(),
nil,
)
for i := range keys {
if err := txn.Delete(keys[i]); err != nil {
return err
}
}
return nil
}
func indexStruct(v reflect.Value, entity Record, keyRoot []byte, id gouuidv6.UUID, path []byte) (keys [][]byte) {
for i := 0; i < v.NumField(); i++ {
fieldType := v.Type().Field(i)
indexName := []byte(fieldType.Name)
if path != nil {
indexName = nestedIndexKeyRoot(path, indexName)
}
if !isTaggedWith(fieldType, tormentaTagNoIndex, tormentaTagNoSave) {
switch fieldType.Type.Kind() {
// Slice: index members individually
case reflect.Slice:
keys = append(keys, getMultipleIndexKeys(v.Field(i), keyRoot, id, indexName)...)
// Array: index members individually
case reflect.Array:
// UUIDV6s are arrays, so we intercept them here
if fieldType.Type == reflect.TypeOf(gouuidv6.UUID{}) {
keys = append(keys, makeIndexKey(keyRoot, id, indexName, v.Field(i).Interface()))
} else {
keys = append(keys, getMultipleIndexKeys(v.Field(i), keyRoot, id, indexName)...)
}
// Strings: either straight index, or split by words
case reflect.String:
if isTaggedWith(fieldType, tormentaTagSplit) {
keys = append(keys, getSplitStringIndexes(v.Field(i), keyRoot, id, indexName)...)
} else {
keys = append(keys, makeIndexKey(keyRoot, id, indexName, v.Field(i).Interface()))
}
// Anonymous/ Nested Structs
case reflect.Struct:
// time.Time is a struct, so we'll intercept it here
// and send it to the index key maker which will translate it to int64
// see below interfaceToBytes for more on that
f := v.Field(i).Interface()
if _, ok := f.(time.Time); ok {
keys = append(keys, makeIndexKey(keyRoot, id, indexName, f))
}
// Recursively index embedded structs
if fieldType.Anonymous {
keys = append(keys, indexStruct(v.Field(i), entity, keyRoot, id, nil)...)
}
// And named structs, if they are tagged 'nested'
// But construct the index with path separators
if isTaggedWith(fieldType, tormentaTagNestedIndex) {
keys = append(keys, indexStruct(v.Field(i), entity, keyRoot, id, indexName)...)
}
default:
keys = append(keys, makeIndexKey(keyRoot, id, indexName, v.Field(i).Interface()))
}
}
}
return
}
// MakeIndexKey constructs an index key
func MakeIndexKey(root []byte, id gouuidv6.UUID, indexName []byte, indexContent interface{}) []byte {
return makeIndexKey(root, id, indexName, indexContent)
}
func makeIndexKey(root []byte, id gouuidv6.UUID, indexName []byte, indexContent interface{}) []byte {
return bytes.Join(
[][]byte{
[]byte(indexKeyPrefix),
root,
indexName,
interfaceToBytes(indexContent),
id.Bytes(),
},
[]byte(keySeparator),
)
}
func getMultipleIndexKeys(v reflect.Value, root []byte, id gouuidv6.UUID, indexName []byte) (keys [][]byte) {
for i := 0; i < v.Len(); i++ {
key := makeIndexKey(root, id, indexName, v.Index(i).Interface())
keys = append(keys, key)
}
return
}
func getSplitStringIndexes(v reflect.Value, root []byte, id gouuidv6.UUID, indexName []byte) (keys [][]byte) {
strings := strings.Split(v.String(), " ")
// Clean non-content words
strings = removeNonContentWords(strings)
for _, s := range strings {
key := makeIndexKey(root, id, indexName, s)
keys = append(keys, key)
}
return
}
// interfaceToBytes encodes values to bytes where the underlying interface is the same as the one we want to encode to. This is used for indexing struct field values where the interface is taken straight from the field value. The only 'manipulation' required is to cast variable length ints and uints to 32bit length.
func interfaceToBytes(value interface{}) []byte {
if value == nil {
return []byte{}
}
buf := new(bytes.Buffer)
switch reflect.ValueOf(value).Type().Kind() {
case reflect.Int:
b, _ := interfaceToInt64(value)
binary.Write(buf, binary.BigEndian, int32(b))
return flipInt(buf.Bytes())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
binary.Write(buf, binary.BigEndian, value)
return flipInt(buf.Bytes())
case reflect.Float64, reflect.Float32:
binary.Write(buf, binary.BigEndian, value)
return flipFloat(buf.Bytes())
case reflect.Uint:
b, _ := interfaceToUint64(value)
binary.Write(buf, binary.BigEndian, uint32(b))
return buf.Bytes()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
binary.Write(buf, binary.BigEndian, value)
return buf.Bytes()
case reflect.Bool:
binary.Write(buf, binary.BigEndian, value)
return buf.Bytes()
case reflect.Struct:
// time.Time is a struct, so we encode/decode as int64 (unix seconds)
if t, ok := reflect.ValueOf(value).Interface().(time.Time); ok {
binary.Write(buf, binary.BigEndian, t.Unix())
return flipInt(buf.Bytes())
}
}
// Everything else as a string (lower case)
return []byte(strings.ToLower(fmt.Sprintf("%v", value)))
}
// interfaceToBytes encodes values to bytes where the input interface could potentially be anything. This is used when consulting indices, rather than building them, and thus the input value is provided by the user. The technique we use is to work out the target type from the original struct field and provide it as an argument to this function. We then do our best to cast the provided value into the target type. The best way I've found to do this is for the number types is to output a string and then parse it as a 64bit int, uint or float and then cast to the target numerical type as required. For bools, if the provided interface is not a bool, we try decoding it as a string (e.g. "false", "f") and then just defualt to False.
func interfaceToBytesWithOverride(value interface{}, typeOverride reflect.Kind) ([]byte, error) {
if value == nil {
return []byte{}, nil
}
buf := new(bytes.Buffer)
switch typeOverride {
// Int
case reflect.Int:
b, err := interfaceToInt64(value)
binary.Write(buf, binary.BigEndian, int32(b))
return flipInt(buf.Bytes()), err
case reflect.Int8:
b, err := interfaceToInt64(value)
binary.Write(buf, binary.BigEndian, int8(b))
return flipInt(buf.Bytes()), err
case reflect.Int16:
b, err := interfaceToInt64(value)
binary.Write(buf, binary.BigEndian, int16(b))
return flipInt(buf.Bytes()), err
case reflect.Int32:
b, err := interfaceToInt64(value)
binary.Write(buf, binary.BigEndian, int32(b))
return flipInt(buf.Bytes()), err
case reflect.Int64:
b, err := interfaceToInt64(value)
binary.Write(buf, binary.BigEndian, b)
return flipInt(buf.Bytes()), err
// Uint
case reflect.Uint:
b, err := interfaceToUint64(value)
binary.Write(buf, binary.BigEndian, uint32(b))
return buf.Bytes(), err
case reflect.Uint8:
b, err := interfaceToUint64(value)
binary.Write(buf, binary.BigEndian, uint8(b))
return buf.Bytes(), err
case reflect.Uint16:
b, err := interfaceToUint64(value)
binary.Write(buf, binary.BigEndian, uint16(b))
return buf.Bytes(), err
case reflect.Uint32:
b, err := interfaceToUint64(value)
binary.Write(buf, binary.BigEndian, uint32(b))
return buf.Bytes(), err
case reflect.Uint64:
b, err := interfaceToUint64(value)
binary.Write(buf, binary.BigEndian, b)
return buf.Bytes(), err
// Float
case reflect.Float32:
b, err := interfaceToFloat64(value)
binary.Write(buf, binary.BigEndian, float32(b))
return flipFloat(buf.Bytes()), err
case reflect.Float64:
b, err := interfaceToFloat64(value)
binary.Write(buf, binary.BigEndian, b)
return flipFloat(buf.Bytes()), err
case reflect.Bool:
b, err := interfaceToBool(value)
binary.Write(buf, binary.BigEndian, b)
return buf.Bytes(), err
case reflect.Struct:
// time.Time is a struct, so we encode/decode as int64 (unix seconds)
if t, ok := reflect.ValueOf(value).Interface().(time.Time); ok {
binary.Write(buf, binary.BigEndian, t.Unix())
return flipInt(buf.Bytes()), nil
}
}
// Everything else as a string (lower case)
return []byte(strings.ToLower(fmt.Sprintf("%v", value))), nil
}
// BIT ORDERING HELPERS
func flipInt(b []byte) []byte {
// Deal with 0
if len(b) == 0 {
return b
}
b[0] ^= 1 << 7
return b
}
func flipFloat(b []byte) []byte {
// Deal with 0
if len(b) == 0 {
return b
}
if b[0]>>7 > 0 {
for i, bb := range b {
b[i] = ^bb
}
} else {
b[0] ^= 0x80
}
return b
}
func revertFloat(b []byte) []byte {
// Deal with 0
if len(b) == 0 {
return b
}
if b[0]>>7 > 0 {
b[0] ^= 0x80
} else {
for i, bb := range b {
b[i] = ^bb
}
}
return b
}
// CONVERSIONS
func interfaceToInt64(i interface{}) (int64, error) {
return strconv.ParseInt(fmt.Sprint(i), 10, 64)
}
func interfaceToUint64(i interface{}) (uint64, error) {
return strconv.ParseUint(fmt.Sprint(i), 10, 64)
}
func interfaceToFloat64(i interface{}) (float64, error) {
return strconv.ParseFloat(fmt.Sprint(i), 10)
}
func interfaceToBool(i interface{}) (bool, error) {
switch i.(type) {
case bool:
return i.(bool), nil
case string:
s := strings.ToLower(i.(string))
if s == "true" || s == "t" {
return true, nil
} else if s == "false" || s == "f" {
return false, nil
}
return false, fmt.Errorf(ErrIndexTypeBool, s)
}
return false, fmt.Errorf(ErrIndexTypeBool, i)
}
// isNegative uses the bitwise &operator to determine if the first bit of a bit slice is 1.
// In the case of signed numbers, this means its a negative
// func isNegative(b []byte) bool {
// return b[0]>>7 > 0
// }
// I think a simple modification can extend this to negative numbers: XOR all positive numbers with 0x8000... and negative numbers with 0xffff.... This should flip the sign bit on both (so negative numbers go first), and then reverse the ordering on negative numbers. Does anyone see a problem with this?
// func signByteFloat(b []byte) []byte {
// return b
// }