OLD | NEW |
---|---|
(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 } | |
OLD | NEW |