-
Notifications
You must be signed in to change notification settings - Fork 5
/
get.go
127 lines (105 loc) · 3.24 KB
/
get.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
package tormenta
import (
"reflect"
"sync"
"github.com/dgraph-io/badger"
"github.com/jpincas/gouuidv6"
)
const (
ErrNoID = "Cannot get entity %s - ID is nil"
)
func (db DB) get(txn *badger.Txn, entity Record, ctx map[string]interface{}, ids ...gouuidv6.UUID) (bool, error) {
// If an override id has been specified, set it on the entity
if len(ids) > 0 {
entity.SetID(ids[0])
}
// We are not treating 'not found' as an actual error,
// instead we return 'false' and nil (unless there is an actual error)
item, err := txn.Get(newContentKey(KeyRoot(entity), entity.GetID()).bytes())
if err == badger.ErrKeyNotFound {
return false, nil
} else if err != nil {
return false, err
}
if err := item.Value(func(val []byte) error {
return db.unserialise(val, entity)
}); err != nil {
return false, err
}
entity.GetCreated()
entity.PostGet(ctx)
return true, nil
}
type getResult struct {
id gouuidv6.UUID
record Record
found bool
err error
}
func (db DB) getIDsWithContext(txn *badger.Txn, target interface{}, ctx map[string]interface{}, ids ...gouuidv6.UUID) (int, error) {
ch := make(chan getResult)
defer close(ch)
var wg sync.WaitGroup
for _, id := range ids {
wg.Add(1)
// It's inefficient creating a new entity target for the result
// on every loop, but we can't just create a single one
// and reuse it, because there would be risk of data from 'previous'
// entities 'infecting' later ones if a certain field wasn't present
// in that later entity, but was in the previous one.
// Unlikely if the all JSON is saved with the schema, but I don't
// think we can risk it
go func(thisRecord Record, thisID gouuidv6.UUID) {
found, err := db.get(txn, thisRecord, ctx, thisID)
ch <- getResult{
id: thisID,
record: thisRecord,
found: found,
err: err,
}
}(newRecordFromSlice(target), id)
}
var resultsList []Record
var errorsList []error
go func() {
for getResult := range ch {
if getResult.err != nil {
errorsList = append(errorsList, getResult.err)
} else if getResult.found {
resultsList = append(resultsList, getResult.record)
}
// Only signal to the wait group that a record has been fetched
// at this point rather than the anonymous func above, otherwise
// you tend to lose the last result
wg.Done()
}
}()
// Once all the results are in, we need to
// sort them according to the original order
// But we'll bail now if there were any errors
wg.Wait()
if len(errorsList) > 0 {
return 0, errorsList[0]
}
return sortToOriginalIDsOrder(target, resultsList, ids), nil
}
func sortToOriginalIDsOrder(target interface{}, resultList []Record, ids []gouuidv6.UUID) (counter int) {
resultMap := map[gouuidv6.UUID]Record{}
for _, record := range resultList {
resultMap[record.GetID()] = record
}
records := newResultsArray(target)
// Remember, we didn't bail if a record was not found
// so there is a chance it won't be in the map - thats ok - just keep count
// of the ones that are there
for _, id := range ids {
record, found := resultMap[id]
if found {
records = reflect.Append(records, recordValue(record))
counter++
}
}
// Set the accumulated results back onto the target
setResultsArrayOntoTarget(target, records)
return counter
}