Chromium Code Reviews| 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 |