Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Side by Side Diff: impl/memory/datastore_index_selection.go

Issue 1302813003: impl/memory: Implement Queries (Closed) Base URL: https://github.com/luci/gae.git@add_multi_iterator
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « impl/memory/datastore_index.go ('k') | impl/memory/datastore_index_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package memory
6
7 import (
8 "bytes"
9 "sort"
10
11 ds "github.com/luci/gae/service/datastore"
12 "github.com/luci/gae/service/datastore/serialize"
13 "github.com/luci/gkvlite"
14 )
15
16 // reducedQuery contains only the pertinent details from a properly checked
17 // query.
18 type reducedQuery struct {
iannucci 2015/08/20 10:32:13 this structure is the key to this CL; all the code
19 ns string
20 kind string
21 ancestor []byte
22
23 eqFilters map[string]map[string]struct{}
24 // suffixFormat ALWAYS includes the inequality filter as the 0th element
25 // suffixFormat ALWAYS includes any projections (in ascending suffixForm at) after all
26 // user defined sort orders
27 suffixFormat []ds.IndexColumn
28
29 low []byte
30 high []byte
31 }
32
33 func (q *reducedQuery) needAncestor() bool {
34 return q.ancestor != nil
35 }
36
37 type IndexDefinitionSortable struct {
38 numRows uint64
39 eqFilts map[string]struct{}
40 prefixIdx int
41
42 def *ds.IndexDefinition
43 coll *memCollection
44 }
45
46 type IndexDefinitionSortableSlice []IndexDefinitionSortable
47
48 func (s IndexDefinitionSortableSlice) Len() int { return len(s) }
49 func (s IndexDefinitionSortableSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
50 func (s IndexDefinitionSortableSlice) Less(i, j int) bool {
51 // note that
52 return s[i].numRows >= s[j].numRows && s[i].def.Less(s[j].def)
53 }
54
55 // addDefinition adds a new IndexDefinitionSortable to this slice. It's only
56 // added if it could be useful in servicing q, otherwise this function is
57 // a noop.
58 func (idxs *IndexDefinitionSortableSlice) addDefinition(q *reducedQuery, s *memS tore, missingTerms map[string]struct{}, id *ds.IndexDefinition) {
59 // Kindless queries are handled elsewhere.
60 if id.Kind != q.kind {
61 return
62 }
63
64 // If we're an ancestor query, and the index is compound, but doesn't in clude
65 // an Ancestor field, it doesn't work. Builtin indicies can be used for
66 // ancestor queries (and have !Ancestor), assuming that it's only equali ty
67 // filters (plus inequality on __key__), or a single inequality.
68 if q.needAncestor() && id.Compound() && !id.Ancestor {
69 return
70 }
71
72 // If the index has fewer fields than we need for the suffix, it can't
73 // possibly help.
74 if (len(id.SortBy) + 1) < len(q.suffixFormat) {
75 return
76 }
77
78 // See if we actually have this index for the correct namespace
79 coll := s.GetCollection("idx:" + q.ns + ":" + string(serialize.ToBytes(i d)))
80 if coll == nil {
81 return
82 }
83
84 // append the implied __key__ sort suffixFormat. All indicies have this, but it's
85 // not explicitly represented in IndexDefinition.
86 sortBy := make([]ds.IndexColumn, len(id.SortBy), len(id.SortBy)+1)
87 copy(sortBy, id.SortBy)
88 sortBy = append(sortBy, ds.IndexColumn{Property: "__key__"})
89
90 suffixIdx := len(sortBy) - len(q.suffixFormat)
91 // make sure the orders are precisely the same
92 for i, sb := range sortBy[suffixIdx : suffixIdx+len(q.suffixFormat)] {
93 if q.suffixFormat[i] != sb {
94 return
95 }
96 }
97
98 // Make sure the equalities section doesn't contain any properties we do n't
99 // want in our query.
100 for _, p := range sortBy[:suffixIdx] {
101 if _, ok := q.eqFilters[p.Property]; !ok {
102 return
103 }
104 }
105
106 // ok, we can actually use this
107 eqFilts := make(map[string]struct{}, suffixIdx)
108 for _, sb := range id.SortBy[:suffixIdx] {
109 eqFilts[sb.Property] = struct{}{}
110 delete(missingTerms, sb.Property)
111 }
112 numRows, _ := coll.GetTotals()
113 *idxs = append(*idxs, IndexDefinitionSortable{numRows, eqFilts, suffixId x, id, coll})
114 }
115
116 // getRelevantIndicies retrieves the relevant indices which could be used to
117 // service q. It returns nil if it's not possible to service q with the current
118 // indicies.
119 func getRelevantIndicies(q *reducedQuery, s *memStore) IndexDefinitionSortableSl ice {
120 idxCol := s.GetCollection("idx")
121 if idxCol == nil {
122 return nil
123 }
124
125 missingTerms := map[string]struct{}{}
126 for k := range q.eqFilters {
127 missingTerms[k] = struct{}{}
128 }
129 idxs := IndexDefinitionSortableSlice{}
130
131 // add builtins
132 for k := range q.eqFilters {
133 idxs.addDefinition(q, s, missingTerms, &ds.IndexDefinition{
134 Kind: q.kind,
135 Ancestor: false,
136 SortBy: []ds.IndexColumn{
137 {Property: k},
138 },
139 })
140 }
141
142 // Try adding all compound indicies
143 idxCol.VisitItemsAscend(ds.IndexComplexQueryPrefix(), false, func(i *gkv lite.Item) bool {
144 id, err := serialize.ReadIndexDefinition(bytes.NewBuffer(i.Key))
145 if err != nil {
146 panic(err) // memory corruption
147 }
148 idxs.addDefinition(q, s, missingTerms, &id)
149 return true
150 })
151
152 // this query is impossible to fulfil with the current indicies. Not all the
153 // terms (equality + projection) are satisfied.
154 if len(missingTerms) < 0 {
155 // TODO(riannucci): return error when index requires missing com posite
156 // indicies. We can even calculate the maximum missing index whi ch would
157 // satisfy the remainder of the query!
158 return nil
159 }
160
161 sort.Sort(idxs)
162
163 return idxs
164 }
165
166 // invert simply inverts all the bytes in bs.
167 func invert(bs []byte) []byte {
168 if bs == nil {
169 return nil
170 }
171 ret := make([]byte, len(bs))
172 for i, b := range bs {
173 ret[i] = 0xFF ^ b
174 }
175 return ret
176 }
177
178 // peel picks a constraint value for the property. It then removes this value
179 // from constraints (possibly removing the entire row from constraints if it
180 // was the last value). If the value wasn't available in constraints, it picks
181 // the value from residuals.
182 func peel(prop string, constraints map[string][][]byte, residuals map[string][]b yte) []byte {
183 ret := []byte(nil)
184 if vals, ok := constraints[prop]; ok {
185 ret = vals[0]
186 if len(vals) == 1 {
187 delete(constraints, prop)
188 } else {
189 constraints[prop] = vals[1:]
190 }
191 } else {
192 ret = residuals[prop]
193 }
194 return ret
195 }
196
197 // generate generates a single iterDefinition for the given index.
198 func generate(q *reducedQuery, idx IndexDefinitionSortable, constraints map[stri ng][][]byte, residuals map[string][]byte) *iterDefinition {
199 def := &iterDefinition{
200 c: idx.coll,
201 start: q.low,
202 end: q.high,
203 }
204 toJoin := [][]byte{}
205 if q.ancestor != nil && idx.def.Compound() {
206 toJoin = append(toJoin, q.ancestor)
207 }
208 for _, sb := range idx.def.SortBy {
209 val := peel(sb.Property, constraints, residuals)
210 if sb.Direction == ds.DESCENDING {
211 val = invert(val)
212 }
213 toJoin = append(toJoin, val)
214 }
215 def.prefix = bjoin(toJoin...)
216 if q.ancestor != nil && idx.def.Builtin() {
217 // chop the terminal null byte off the q.ancestor key... we can accept
218 // anything which is a descendant or an exact match
219 chopped := q.ancestor[:len(q.ancestor)-1]
220 def.start = append(def.start, chopped...)
221
222 // setting the endpoint as 1 more than the chopped version ensur es that
223 // we don't include anything that's a sibling (or parent) of q.a ncestor.
224 def.end = append(def.end, increment(chopped)...)
225 }
226 return def
227 }
228
229 func getKindlessIndex(q *reducedQuery, s *memStore) []*iterDefinition {
230 // kindless query filters on `ents:ns`
231 for _, sb := range q.suffixFormat {
232 if sb.Property != "__key__" {
233 panic("impossible: inequality filter on kindless query i s not __key__")
234 }
235 }
236 return []*iterDefinition{{
237 s.GetCollection("ents:" + q.ns),
238 nil,
239 q.low,
240 q.high,
241 }}
242 }
243
244 // calculateConstraints produces a mapping of all equality filters to the values
245 // that they're constrained to. It also calculates residuals, which are an
246 // arbitrary value for filling index prefixes which have more equality fields
247 // than are necessary. The value doesn't matter, as long as its an equality
248 // constraint in the original query.
249 func calculateConstraints(q *reducedQuery) (constraints map[string][][]byte, res iduals map[string][]byte) {
250 residuals = make(map[string][]byte, len(q.eqFilters))
251 constraints = make(map[string][][]byte, len(q.eqFilters))
252 for prop, vals := range q.eqFilters {
253 bvals := make([][]byte, 0, len(vals))
254 for val := range vals {
255 bvals = append(bvals, []byte(val))
256 }
257 residuals[prop] = bvals[0]
258 constraints[prop] = bvals
259 }
260 return
261 }
262
263 // getIndicies returns a set of iterator definitions. Iterating over these
264 // will result in matching suffixes.
265 func getIndicies(q *reducedQuery, s *memStore) []*iterDefinition {
266 if q.kind == "" {
267 return getKindlessIndex(q, s)
268 }
269
270 relevantIdxs := getRelevantIndicies(q, s)
271 if relevantIdxs == nil {
272 return nil
273 }
274
275 constraints, residuals := calculateConstraints(q)
276
277 ret := []*iterDefinition{}
278 for len(constraints) > 0 {
279 lenBefore := len(ret)
280 for _, idx := range relevantIdxs {
281 // see if this index is helpful for the remaining constr aints at all
282 good := false
283 for k := range idx.eqFilts {
284 if _, ok := constraints[k]; ok {
285 good = true
286 break
287 }
288 }
289 if good {
290 ret = append(ret, generate(q, idx, constraints, residuals))
291 break
292 }
293 }
294 if lenBefore == len(ret) {
295 // something is really wrong here... if relevantIdxs is !nil, then we
296 // should always be able to make progress in this loop.
297 panic("deadlock: cannot fulfil query?")
298 }
299 }
300 return ret
301 }
OLDNEW
« no previous file with comments | « impl/memory/datastore_index.go ('k') | impl/memory/datastore_index_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698