 Chromium Code Reviews
 Chromium Code Reviews Issue 1286943002:
  impl/memory: Make queries self-validate  (Closed) 
  Base URL: https://github.com/luci/gae.git@add_datastore_testable
    
  
    Issue 1286943002:
  impl/memory: Make queries self-validate  (Closed) 
  Base URL: https://github.com/luci/gae.git@add_datastore_testable| OLD | NEW | 
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 package memory | 5 package memory | 
| 6 | 6 | 
| 7 import ( | 7 import ( | 
| 8 "bytes" | |
| 8 "errors" | 9 "errors" | 
| 9 "fmt" | 10 "fmt" | 
| 10 "math" | 11 "math" | 
| 11 "strings" | 12 "strings" | 
| 12 | 13 | 
| 13 ds "github.com/luci/gae/service/datastore" | 14 ds "github.com/luci/gae/service/datastore" | 
| 14 "github.com/luci/gkvlite" | |
| 15 ) | 15 ) | 
| 16 | 16 | 
| 17 const MaxQueryComponents = 100 | |
| 
dnj (Google)
2015/08/14 21:01:35
Is this a hard limit, or one you chose? Either way
 
iannucci
2015/08/14 22:25:47
It has the same amount of documentation as the con
 | |
| 18 | |
| 19 var errQueryDone = errors.New("query is done") | |
| 20 | |
| 17 type queryOp int | 21 type queryOp int | 
| 18 | 22 | 
| 19 const ( | 23 const ( | 
| 20 qInvalid queryOp = iota | 24 qInvalid queryOp = iota | 
| 21 qEqual | 25 qEqual | 
| 22 qLessThan | 26 qLessThan | 
| 23 qLessEq | 27 qLessEq | 
| 24 qGreaterEq | 28 qGreaterEq | 
| 25 qGreaterThan | 29 qGreaterThan | 
| 26 ) | 30 ) | 
| 27 | 31 | 
| 28 func (o queryOp) isEQOp() bool { | |
| 29 return o == qEqual | |
| 30 } | |
| 31 | |
| 32 func (o queryOp) isINEQOp() bool { | |
| 33 return o >= qLessThan && o <= qGreaterThan | |
| 34 } | |
| 35 | |
| 36 var queryOpMap = map[string]queryOp{ | 32 var queryOpMap = map[string]queryOp{ | 
| 37 "=": qEqual, | 33 "=": qEqual, | 
| 38 "<": qLessThan, | 34 "<": qLessThan, | 
| 39 "<=": qLessEq, | 35 "<=": qLessEq, | 
| 40 ">=": qGreaterEq, | 36 ">=": qGreaterEq, | 
| 41 ">": qGreaterThan, | 37 ">": qGreaterThan, | 
| 42 } | 38 } | 
| 43 | 39 | 
| 44 type queryFilter struct { | 40 type queryFilter struct { | 
| 45 prop string | 41 prop string | 
| 46 op queryOp | 42 op queryOp | 
| 47 value interface{} | 43 value interface{} | 
| 48 } | 44 } | 
| 49 | 45 | 
| 50 func parseFilter(f string, v interface{}) (ret queryFilter, err error) { | 46 func parseFilter(f string) (prop string, op queryOp, err error) { | 
| 51 toks := strings.SplitN(strings.TrimSpace(f), " ", 2) | 47 toks := strings.SplitN(strings.TrimSpace(f), " ", 2) | 
| 52 if len(toks) != 2 { | 48 if len(toks) != 2 { | 
| 53 err = errors.New("datastore: invalid filter: " + f) | 49 err = errors.New("datastore: invalid filter: " + f) | 
| 54 } else { | 50 } else { | 
| 55 » » op := queryOpMap[toks[1]] | 51 » » op = queryOpMap[toks[1]] | 
| 56 if op == qInvalid { | 52 if op == qInvalid { | 
| 57 err = fmt.Errorf("datastore: invalid operator %q in filt er %q", toks[1], f) | 53 err = fmt.Errorf("datastore: invalid operator %q in filt er %q", toks[1], f) | 
| 58 } else { | 54 } else { | 
| 59 » » » ret.prop = toks[0] | 55 » » » prop = toks[0] | 
| 60 » » » ret.op = op | |
| 61 » » » ret.value = v | |
| 62 } | 56 } | 
| 63 } | 57 } | 
| 64 return | 58 return | 
| 65 } | 59 } | 
| 66 | 60 | 
| 67 type queryCursor string | 61 type queryCursor string | 
| 68 | 62 | 
| 69 func (q queryCursor) String() string { return string(q) } | 63 func (q queryCursor) String() string { return string(q) } | 
| 70 func (q queryCursor) Valid() bool { return q != "" } | 64 func (q queryCursor) Valid() bool { return q != "" } | 
| 71 | 65 | 
| 66 type queryIneqFilter struct { | |
| 67 prop string | |
| 68 | |
| 69 low string | |
| 70 high string | |
| 71 } | |
| 72 | |
| 73 func increment(bstr string, positive bool) string { | |
| 74 lastIdx := len(bstr) - 1 | |
| 
dnj (Google)
2015/08/14 21:01:35
Can we have an empty bstr?
 
iannucci
2015/08/14 22:25:47
nope
 | |
| 75 last := bstr[lastIdx] | |
| 76 if positive { | |
| 77 if last == 0xFF { | |
| 78 return bstr + "\x00" | |
| 79 } | |
| 80 return bstr[:lastIdx-1] + string(last+1) | |
| 81 } else { | |
| 82 if last == 0 { | |
| 83 return bstr[:lastIdx-1] | |
| 84 } | |
| 85 return bstr[:lastIdx-1] + string(last-1) | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 func (q *queryIneqFilter) constrain(op queryOp, val string) bool { | |
| 90 switch op { | |
| 91 case qLessThan: | |
| 92 val = increment(val, true) | |
| 93 fallthrough | |
| 94 case qLessEq: | |
| 95 // adjust upper bound downwards | |
| 96 if q.high > val { | |
| 97 q.high = val | |
| 98 } | |
| 99 | |
| 100 case qGreaterThan: | |
| 101 val = increment(val, false) | |
| 102 fallthrough | |
| 103 case qGreaterEq: | |
| 104 // adjust lower bound upwards | |
| 105 if q.low < val { | |
| 106 q.low = val | |
| 107 } | |
| 108 } | |
| 109 | |
| 110 return q.low > q.high | |
| 111 } | |
| 112 | |
| 72 type queryImpl struct { | 113 type queryImpl struct { | 
| 73 ns string | 114 ns string | 
| 74 | 115 | 
| 75 kind string | 116 kind string | 
| 76 ancestor ds.Key | 117 ancestor ds.Key | 
| 77 » filter []queryFilter | 118 | 
| 78 » order []ds.IndexColumn | 119 » // prop -> encoded values | 
| 79 » project []string | 120 » eqFilters map[string]map[string]struct{} | 
| 121 » ineqFilter queryIneqFilter | |
| 122 » order []ds.IndexColumn | |
| 123 » project map[string]struct{} | |
| 80 | 124 | 
| 81 distinct bool | 125 distinct bool | 
| 82 eventualConsistency bool | 126 eventualConsistency bool | 
| 83 keysOnly bool | 127 keysOnly bool | 
| 84 limit int32 | 128 limit int32 | 
| 85 offset int32 | 129 offset int32 | 
| 86 | 130 | 
| 87 start queryCursor | 131 start queryCursor | 
| 88 end queryCursor | 132 end queryCursor | 
| 89 | 133 | 
| 90 err error | 134 err error | 
| 91 } | 135 } | 
| 92 | 136 | 
| 93 var _ ds.Query = (*queryImpl)(nil) | 137 var _ ds.Query = (*queryImpl)(nil) | 
| 94 | 138 | 
| 95 func (q *queryImpl) normalize() (ret *queryImpl) { | 139 func (q *queryImpl) valid(ns string, isTxn bool) (done bool, err error) { | 
| 96 » // ported from GAE SDK datastore_index.py;Normalize() | 140 » if q.err == errQueryDone { | 
| 97 » ret = q.clone() | 141 » » done = true | 
| 98 | 142 » } else if q.err != nil { | 
| 99 » bs := newMemStore() | 143 » » err = q.err | 
| 100 | 144 » } else if ns != q.ns { | 
| 101 » eqProperties := bs.MakePrivateCollection(nil) | 145 » » err = errors.New( | 
| 102 | |
| 103 » ineqProperties := bs.MakePrivateCollection(nil) | |
| 104 | |
| 105 » for _, f := range ret.filter { | |
| 106 » » // if we supported the IN operator, we would check to see if the re were | |
| 107 » » // multiple value operands here, but the go SDK doesn't support this. | |
| 108 » » if f.op.isEQOp() { | |
| 109 » » » eqProperties.Set([]byte(f.prop), []byte{}) | |
| 110 » » } else if f.op.isINEQOp() { | |
| 111 » » » ineqProperties.Set([]byte(f.prop), []byte{}) | |
| 112 » » } | |
| 113 » } | |
| 114 | |
| 115 » ineqProperties.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool { | |
| 116 » » eqProperties.Delete(i.Key) | |
| 117 » » return true | |
| 118 » }) | |
| 119 | |
| 120 » removeSet := bs.MakePrivateCollection(nil) | |
| 121 » eqProperties.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool { | |
| 122 » » removeSet.Set(i.Key, []byte{}) | |
| 123 » » return true | |
| 124 » }) | |
| 125 | |
| 126 » newOrders := []ds.IndexColumn{} | |
| 127 » for _, o := range ret.order { | |
| 128 » » if removeSet.Get([]byte(o.Property)) == nil { | |
| 129 » » » removeSet.Set([]byte(o.Property), []byte{}) | |
| 130 » » » newOrders = append(newOrders, o) | |
| 131 » » } | |
| 132 » } | |
| 133 » ret.order = newOrders | |
| 134 | |
| 135 » // need to fix ret.filters if we ever support the EXISTS operator and/or | |
| 136 » // projections. | |
| 137 » // | |
| 138 » // newFilters = [] | |
| 139 » // for f in ret.filters: | |
| 140 » // if f.op != qExists: | |
| 141 » // newFilters = append(newFilters, f) | |
| 142 » // if !removeSet.Has(f.prop): | |
| 143 » // removeSet.InsertNoReplace(f.prop) | |
| 144 » // newFilters = append(newFilters, f) | |
| 145 » // | |
| 146 » // so ret.filters == newFilters becuase none of ret.filters has op == qE xists | |
| 147 » // | |
| 148 » // then: | |
| 149 » // | |
| 150 » // for prop in ret.project: | |
| 151 » // if !removeSet.Has(prop): | |
| 152 » // removeSet.InsertNoReplace(prop) | |
| 153 » // ... make new EXISTS filters, add them to newFilters ... | |
| 154 » // ret.filters = newFilters | |
| 155 » // | |
| 156 » // However, since we don't support projection queries, this is moot. | |
| 157 | |
| 158 » if eqProperties.Get([]byte("__key__")) != nil { | |
| 159 » » ret.order = []ds.IndexColumn{} | |
| 160 » } | |
| 161 | |
| 162 » newOrders = []ds.IndexColumn{} | |
| 163 » for _, o := range ret.order { | |
| 164 » » if o.Property == "__key__" { | |
| 165 » » » newOrders = append(newOrders, o) | |
| 166 » » » break | |
| 167 » » } | |
| 168 » » newOrders = append(newOrders, o) | |
| 169 » } | |
| 170 » ret.order = newOrders | |
| 171 | |
| 172 » return | |
| 173 } | |
| 174 | |
| 175 func (q *queryImpl) checkCorrectness(ns string, isTxn bool) (ret *queryImpl) { | |
| 176 » // ported from GAE SDK datastore_stub_util.py;CheckQuery() | |
| 177 » ret = q.clone() | |
| 178 | |
| 179 » if ns != ret.ns { | |
| 180 » » ret.err = errors.New( | |
| 181 "gae/memory: Namespace mismatched. Query and Datastore d on't agree " + | 146 "gae/memory: Namespace mismatched. Query and Datastore d on't agree " + | 
| 182 "on the current namespace") | 147 "on the current namespace") | 
| 183 » » return | 148 » } else if isTxn && q.ancestor == nil { | 
| 184 » } | 149 » » err = errors.New( | 
| 185 | |
| 186 » if ret.err != nil { | |
| 187 » » return | |
| 188 » } | |
| 189 | |
| 190 » // if projection && keys_only: | |
| 191 » // "projection and keys_only cannot both be set" | |
| 192 | |
| 193 » // if projection props match /^__.*__$/: | |
| 194 » // "projections are not supported for the property: %(prop)s" | |
| 195 | |
| 196 » if isTxn && ret.ancestor == nil { | |
| 197 » » ret.err = errors.New( | |
| 198 "gae/memory: Only ancestor queries are allowed inside tr ansactions") | 150 "gae/memory: Only ancestor queries are allowed inside tr ansactions") | 
| 199 » » return | 151 » } else if q.numComponents() > MaxQueryComponents { | 
| 200 » } | 152 » » err = fmt.Errorf( | 
| 201 | 153 » » » "gae/memory: query is too large. may not have more than "+ | 
| 202 » numComponents := len(ret.filter) + len(ret.order) | 154 » » » » "%d filters + sort orders + ancestor total: had %d", | 
| 203 » if ret.ancestor != nil { | 155 » » » MaxQueryComponents, q.numComponents()) | 
| 204 » » numComponents++ | 156 » } else if len(q.project) == 0 && q.distinct { | 
| 205 » } | 157 » » err = errors.New( | 
| 206 » if numComponents > 100 { | 158 » » » "gae/memory: Distinct() only makes sense on projection q ueries.") | 
| 207 » » ret.err = errors.New( | |
| 208 » » » "gae/memory: query is too large. may not have more than " + | |
| 209 » » » » "100 filters + sort orders ancestor total") | |
| 210 » } | |
| 211 | |
| 212 » // if ret.ancestor.appid() != current appid | |
| 213 » // "query app is x but ancestor app is x" | |
| 214 » // if ret.ancestor.namespace() != current namespace | |
| 215 » // "query namespace is x but ancestor namespace is x" | |
| 216 | |
| 217 » // if not all(g in orders for g in group_by) | |
| 218 » // "items in the group by clause must be specified first in the orderin g" | |
| 219 | |
| 220 » ineqPropName := "" | |
| 221 » for _, f := range ret.filter { | |
| 222 » » if f.prop == "__key__" { | |
| 223 » » » k, ok := f.value.(ds.Key) | |
| 224 » » » if !ok { | |
| 225 » » » » ret.err = errors.New( | |
| 226 » » » » » "gae/memory: __key__ filter value must b e a Key") | |
| 227 » » » » return | |
| 228 » » » } | |
| 229 » » » if !ds.KeyValid(k, false, globalAppID, q.ns) { | |
| 230 » » » » // See the comment in queryImpl.Ancestor; basica lly this check | |
| 231 » » » » // never happens in the real env because the SDK silently swallows | |
| 232 » » » » // this condition :/ | |
| 233 » » » » ret.err = ds.ErrInvalidKey | |
| 234 » » » » return | |
| 235 » » » } | |
| 236 » » » if k.Namespace() != ns { | |
| 237 » » » » ret.err = fmt.Errorf("bad namespace: %q (expecte d %q)", k.Namespace(), ns) | |
| 238 » » » » return | |
| 239 » » » } | |
| 240 » » » // __key__ filter app is X but query app is X | |
| 241 » » » // __key__ filter namespace is X but query namespace is X | |
| 242 » » } | |
| 243 » » // if f.op == qEqual and f.prop in ret.project_fields | |
| 244 » » // "cannot use projection on a proprety with an equality filte r" | |
| 245 | |
| 246 » » if f.op.isINEQOp() { | |
| 247 » » » if ineqPropName == "" { | |
| 248 » » » » ineqPropName = f.prop | |
| 249 » » » } else if f.prop != ineqPropName { | |
| 250 » » » » ret.err = fmt.Errorf( | |
| 251 » » » » » "gae/memory: Only one inequality filter per query is supported. "+ | |
| 252 » » » » » » "Encountered both %s and %s", in eqPropName, f.prop) | |
| 253 » » » » return | |
| 254 » » » } | |
| 255 » » } | |
| 256 » } | |
| 257 | |
| 258 » // if ineqPropName != "" && len(group_by) > 0 && len(orders) ==0 | |
| 259 » // "Inequality filter on X must also be a group by property "+ | |
| 260 » // "when group by properties are set." | |
| 261 | |
| 262 » if ineqPropName != "" && len(ret.order) != 0 { | |
| 263 » » if ret.order[0].Property != ineqPropName { | |
| 264 » » » ret.err = fmt.Errorf( | |
| 265 » » » » "gae/memory: The first sort property must be the same as the property "+ | |
| 266 » » » » » "to which the inequality filter is appli ed. In your query "+ | |
| 267 » » » » » "the first sort property is %s but the i nequality filter "+ | |
| 268 » » » » » "is on %s", ret.order[0].Property, ineqP ropName) | |
| 269 » » » return | |
| 270 » » } | |
| 271 » } | |
| 272 | |
| 273 » if ret.kind == "" { | |
| 274 » » for _, f := range ret.filter { | |
| 275 » » » if f.prop != "__key__" { | |
| 276 » » » » ret.err = errors.New( | |
| 277 » » » » » "gae/memory: kind is required for non-__ key__ filters") | |
| 278 » » » » return | |
| 279 » » » } | |
| 280 » » } | |
| 281 » » for _, o := range ret.order { | |
| 282 » » » if o.Property != "__key__" || o.Direction != ds.ASCENDIN G { | |
| 283 » » » » ret.err = errors.New( | |
| 284 » » » » » "gae/memory: kind is required for all or ders except __key__ ascending") | |
| 285 » » » » return | |
| 286 » » » } | |
| 287 » » } | |
| 288 } | 159 } | 
| 289 return | 160 return | 
| 290 } | 161 } | 
| 291 | 162 | 
| 163 func (q *queryImpl) numComponents() int { | |
| 164 numComponents := len(q.order) | |
| 165 if q.ineqFilter.prop != "" { | |
| 166 numComponents++ | |
| 167 } | |
| 168 for _, v := range q.eqFilters { | |
| 169 numComponents += len(v) | |
| 170 } | |
| 171 if q.ancestor != nil { | |
| 172 numComponents++ | |
| 173 } | |
| 174 return numComponents | |
| 175 } | |
| 176 | |
| 292 func (q *queryImpl) calculateIndex() *ds.IndexDefinition { | 177 func (q *queryImpl) calculateIndex() *ds.IndexDefinition { | 
| 293 // as a nod to simplicity in this code, we'll require that a single inde x | 178 // as a nod to simplicity in this code, we'll require that a single inde x | 
| 294 // is able to service the entire query. E.g. no zigzag merge joins or | 179 // is able to service the entire query. E.g. no zigzag merge joins or | 
| 295 // multiqueries. This will mean that the user will need to rely on | 180 // multiqueries. This will mean that the user will need to rely on | 
| 296 // dev_appserver to tell them what indicies they need for real, and for thier | 181 // dev_appserver to tell them what indicies they need for real, and for thier | 
| 297 // tests they'll need to specify the missing composite indices manually. | 182 // tests they'll need to specify the missing composite indices manually. | 
| 298 // | 183 // | 
| 299 // This COULD lead to an exploding indicies problem, but we can fix that when | 184 // This COULD lead to an exploding indicies problem, but we can fix that when | 
| 300 // we get to it. | 185 // we get to it. | 
| 301 | 186 | 
| 302 //sortOrders := []qSortBy{} | 187 //sortOrders := []qSortBy{} | 
| 303 | 188 | 
| 304 return nil | 189 return nil | 
| 305 } | 190 } | 
| 306 | 191 | 
| 307 func (q *queryImpl) clone() *queryImpl { | 192 // checkMutateClone sees if the query has an error. If not, it clones the query, | 
| 308 » ret := *q | 193 // and assigns the output of `check` to the query error slot. If check returns | 
| 309 » ret.filter = append([]queryFilter(nil), q.filter...) | 194 // nil, it calls `mutate` on the cloned query. The (possibly new) query is then | 
| 310 » ret.order = append([]ds.IndexColumn(nil), q.order...) | 195 // returned. | 
| 311 » ret.project = append([]string(nil), q.project...) | 196 func (q *queryImpl) checkMutateClone(check func() error, mutate func(*queryImpl) ) *queryImpl { | 
| 312 » return &ret | 197 » if q.err != nil { | 
| 198 » » return q | |
| 199 » } | |
| 200 » nq := *q | |
| 201 » nq.eqFilters = make(map[string]map[string]struct{}, len(q.eqFilters)) | |
| 202 » for prop, vals := range q.eqFilters { | |
| 203 » » nq.eqFilters[prop] = make(map[string]struct{}, len(vals)) | |
| 204 » » for v := range vals { | |
| 205 » » » nq.eqFilters[prop][v] = struct{}{} | |
| 206 » » } | |
| 207 » } | |
| 208 » nq.order = append([]ds.IndexColumn(nil), q.order...) | |
| 
dnj (Google)
2015/08/14 21:01:35
I used to like doing this; however, "append" will
 
iannucci
2015/08/14 22:25:47
done
 | |
| 209 » nq.project = make(map[string]struct{}, len(q.project)) | |
| 210 » for f := range q.project { | |
| 211 » » nq.project[f] = struct{}{} | |
| 212 » } | |
| 213 » if check != nil { | |
| 214 » » nq.err = check() | |
| 215 » } | |
| 216 » if nq.err == nil { | |
| 217 » » mutate(&nq) | |
| 218 » } | |
| 219 » return &nq | |
| 313 } | 220 } | 
| 314 | 221 | 
| 315 func (q *queryImpl) Ancestor(k ds.Key) ds.Query { | 222 func (q *queryImpl) Ancestor(k ds.Key) ds.Query { | 
| 316 » q = q.clone() | 223 » return q.checkMutateClone( | 
| 317 » q.ancestor = k | 224 » » func() error { | 
| 318 » if k == nil { | 225 » » » if k == nil { | 
| 319 » » // SDK has an explicit nil-check | 226 » » » » // SDK has an explicit nil-check | 
| 320 » » q.err = errors.New("datastore: nil query ancestor") | 227 » » » » return errors.New("datastore: nil query ancestor ") | 
| 321 » } else if !ds.KeyValid(k, false, globalAppID, q.ns) { | 228 » » » } | 
| 322 » » // technically the SDK implementation does a Weird Thing (tm) if both the | 229 » » » if !ds.KeyValid(k, false, globalAppID, q.ns) { | 
| 323 » » // stringID and intID are set on a key; it only serializes the s tringID in | 230 » » » » // technically the SDK implementation does a Wei rd Thing (tm) if both the | 
| 324 » » // the proto. This means that if you set the Ancestor to an inva lid key, | 231 » » » » // stringID and intID are set on a key; it only serializes the stringID in | 
| 325 » » // you'll never actually hear about it. Instead of doing that in sanity, we | 232 » » » » // the proto. This means that if you set the Anc estor to an invalid key, | 
| 326 » » // just swap to an error here. | 233 » » » » // you'll never actually hear about it. Instead of doing that insanity, we | 
| 327 » » q.err = ds.ErrInvalidKey | 234 » » » » // just swap to an error here. | 
| 328 » } else if k.Namespace() != q.ns { | 235 » » » » return ds.ErrInvalidKey | 
| 329 » » q.err = fmt.Errorf("bad namespace: %q (expected %q)", k.Namespac e(), q.ns) | 236 » » » } | 
| 330 » } | 237 » » » if k.Namespace() != q.ns { | 
| 331 » return q | 238 » » » » return fmt.Errorf("bad namespace: %q (expected % q)", k.Namespace(), q.ns) | 
| 239 » » » } | |
| 240 » » » if q.ancestor != nil { | |
| 241 » » » » return errors.New("cannot have more than one anc estor") | |
| 242 » » » } | |
| 243 » » » return nil | |
| 244 » » }, | |
| 245 » » func(q *queryImpl) { | |
| 246 » » » q.ancestor = k | |
| 247 » » }) | |
| 332 } | 248 } | 
| 333 | 249 | 
| 334 func (q *queryImpl) Distinct() ds.Query { | 250 func (q *queryImpl) Distinct() ds.Query { | 
| 335 » q = q.clone() | 251 » return q.checkMutateClone(nil, func(q *queryImpl) { | 
| 336 » q.distinct = true | 252 » » q.distinct = true | 
| 337 » return q | 253 » }) | 
| 338 } | 254 } | 
| 339 | 255 | 
| 340 func (q *queryImpl) Filter(fStr string, val interface{}) ds.Query { | 256 func (q *queryImpl) Filter(fStr string, val interface{}) ds.Query { | 
| 341 » q = q.clone() | 257 » prop := "" | 
| 342 » f, err := parseFilter(fStr, val) | 258 » op := qInvalid | 
| 343 » if err != nil { | 259 » binVal := "" | 
| 344 » » q.err = err | 260 » return q.checkMutateClone( | 
| 345 » » return q | 261 » » func() (err error) { | 
| 
dnj (Google)
2015/08/14 21:01:35
No point in a named variable that you only use it
 
iannucci
2015/08/14 22:25:47
*shrug* changed
 
dnj (Google)
2015/08/14 22:58:13
maruel@ cares :P
 | |
| 346 » } | 262 » » » prop, op, err = parseFilter(fStr) | 
| 347 » q.filter = append(q.filter, f) | 263 » » » if err != nil { | 
| 348 » return q | 264 » » » » return | 
| 265 » » » } | |
| 266 | |
| 267 » » » if q.kind == "" && prop != "__key__" { | |
| 268 » » » » // https://cloud.google.com/appengine/docs/go/da tastore/queries#Go_Kindless_queries | |
| 269 » » » » return fmt.Errorf( | |
| 270 » » » » » "kindless queries can only filter on __k ey__, got %q", fStr) | |
| 271 » » » } | |
| 272 | |
| 273 » » » p := ds.Property{} | |
| 274 » » » err = p.SetValue(val, ds.NoIndex) | |
| 275 » » » if err != nil { | |
| 276 » » » » return err | |
| 
dnj (Google)
2015/08/14 21:01:35
If still using named return variables, just "retur
 
iannucci
2015/08/14 22:25:47
Acknowledged.
 | |
| 277 » » » } | |
| 278 | |
| 279 » » » if p.Type() == ds.PTKey { | |
| 280 » » » » if !ds.KeyValid(p.Value().(ds.Key), false, globa lAppID, q.ns) { | |
| 281 » » » » » return ds.ErrInvalidKey | |
| 282 » » » » } | |
| 283 » » » } | |
| 284 | |
| 285 » » » buf := &bytes.Buffer{} | |
| 
dnj (Google)
2015/08/14 21:01:35
Might as well do this down near return statement,
 
iannucci
2015/08/14 22:25:47
Done.
 | |
| 286 » » » p.Write(buf, ds.WithoutContext) | |
| 287 | |
| 288 » » » if prop == "__key__" { | |
| 289 » » » » if op == qEqual { | |
| 290 » » » » » return fmt.Errorf( | |
| 291 » » » » » » "query equality filter on __key_ _ is silly: %q", fStr) | |
| 292 » » » » } | |
| 293 » » » » if p.Type() != ds.PTKey { | |
| 294 » » » » » return fmt.Errorf("__key__ filter value is not a key: %T", val) | |
| 295 » » » » } | |
| 296 » » » } | |
| 297 | |
| 298 » » » if op != qEqual { | |
| 299 » » » » if q.ineqFilter.prop != "" && q.ineqFilter.prop != prop { | |
| 300 » » » » » return fmt.Errorf( | |
| 301 » » » » » » "inequality filters on multiple properties: %q and %q", | |
| 302 » » » » » » q.ineqFilter.prop, prop) | |
| 303 » » » » } | |
| 304 » » » » if len(q.order) > 0 && q.order[0].Property != pr op { | |
| 305 » » » » » return fmt.Errorf( | |
| 306 » » » » » » "first sort order must match ine quality filter: %q v %q", | |
| 307 » » » » » » q.order[0].Property, prop) | |
| 308 » » » » } | |
| 309 » » » } else if _, ok := q.project[prop]; ok { | |
| 310 » » » » return fmt.Errorf( | |
| 311 » » » » » "cannot project on field which is used i n an equality filter: %q", | |
| 312 » » » » » prop) | |
| 313 » » » } | |
| 314 » » » binVal = buf.String() | |
| 315 » » » return | |
| 316 » » }, | |
| 317 » » func(q *queryImpl) { | |
| 318 » » » if op == qEqual { | |
| 319 » » » » // add it to eq filters | |
| 320 » » » » if _, ok := q.eqFilters[prop]; !ok { | |
| 321 » » » » » q.eqFilters[prop] = map[string]struct{}{ binVal: {}} | |
| 322 » » » » } else { | |
| 323 » » » » » q.eqFilters[prop][binVal] = struct{}{} | |
| 324 » » » » } | |
| 325 | |
| 326 » » » » // remove it from sort orders. | |
| 327 » » » » // https://cloud.google.com/appengine/docs/go/da tastore/queries#sort_orders_are_ignored_on_properties_with_equality_filters | |
| 328 » » » » toRm := -1 | |
| 329 » » » » for i, o := range q.order { | |
| 330 » » » » » if o.Property == prop { | |
| 331 » » » » » » toRm = i | |
| 332 » » » » » » break | |
| 333 » » » » » } | |
| 334 » » » » } | |
| 335 » » » » if toRm >= 0 { | |
| 336 » » » » » q.order = append(q.order[:toRm], q.order [toRm+1:]...) | |
| 337 » » » » } | |
| 338 » » » } else { | |
| 339 » » » » q.ineqFilter.prop = prop | |
| 340 » » » » if !q.ineqFilter.constrain(op, binVal) { | |
| 341 » » » » » q.err = errQueryDone | |
| 342 » » » » } | |
| 343 » » » } | |
| 344 » » }) | |
| 349 } | 345 } | 
| 350 | 346 | 
| 351 func (q *queryImpl) Order(prop string) ds.Query { | 347 func (q *queryImpl) Order(prop string) ds.Query { | 
| 352 » q = q.clone() | 348 » col := ds.IndexColumn{} | 
| 353 » prop = strings.TrimSpace(prop) | 349 » return q.checkMutateClone( | 
| 354 » o := ds.IndexColumn{Property: prop} | 350 » » func() error { | 
| 355 » if strings.HasPrefix(prop, "-") { | 351 » » » // check that first order == first inequality. | 
| 356 » » o.Direction = ds.DESCENDING | 352 » » » // if order is an equality already, ignore it | 
| 357 » » o.Property = strings.TrimSpace(prop[1:]) | 353 » » » col.Property = strings.TrimSpace(prop) | 
| 358 » } else if strings.HasPrefix(prop, "+") { | 354 » » » if strings.HasPrefix(prop, "-") { | 
| 359 » » q.err = fmt.Errorf("datastore: invalid order: %q", prop) | 355 » » » » col.Direction = ds.DESCENDING | 
| 360 » » return q | 356 » » » » col.Property = strings.TrimSpace(prop[1:]) | 
| 361 » } | 357 » » » } else if strings.HasPrefix(prop, "+") { | 
| 
dnj (Google)
2015/08/14 21:01:35
Why specifically "+"? Just catch a common user err
 
iannucci
2015/08/14 22:25:47
this was copied from the sdk.
 | |
| 362 » if len(o.Property) == 0 { | 358 » » » » return fmt.Errorf("datastore: invalid order: %q" , prop) | 
| 363 » » q.err = errors.New("datastore: empty order") | 359 » » » } | 
| 364 » » return q | 360 » » » if len(col.Property) == 0 { | 
| 365 » } | 361 » » » » return errors.New("datastore: empty order") | 
| 366 » q.order = append(q.order, o) | 362 » » » } | 
| 367 » return q | 363 » » » if q.ineqFilter.prop != "" && q.ineqFilter.prop != col.P roperty { | 
| 364 » » » » return fmt.Errorf( | |
| 365 » » » » » "first sort order must match inequality filter: %q v %q", | |
| 366 » » » » » prop, q.ineqFilter.prop) | |
| 367 » » » } | |
| 368 » » » if q.kind == "" && (col.Property != "__key__" || col.Dir ection != ds.ASCENDING) { | |
| 369 » » » » return fmt.Errorf("invalid order for kindless qu ery: %#v", col) | |
| 370 » » » } | |
| 371 » » » return nil | |
| 372 » » }, | |
| 373 » » func(q *queryImpl) { | |
| 374 » » » if _, ok := q.eqFilters[col.Property]; ok { | |
| 375 » » » » // skip it if it's an equality filter | |
| 376 » » » » // https://cloud.google.com/appengine/docs/go/da tastore/queries#sort_orders_are_ignored_on_properties_with_equality_filters | |
| 377 » » » » return | |
| 378 » » » } | |
| 379 » » » for _, order := range q.order { | |
| 380 » » » » if order.Property == col.Property { | |
| 381 » » » » » // can't sort by the same order twice | |
| 382 » » » » » return | |
| 383 » » » » } | |
| 384 » » » } | |
| 385 » » » if col.Property == "__key__" { | |
| 386 » » » » // __key__ order dominates all other orders | |
| 387 » » » » q.order = []ds.IndexColumn{col} | |
| 388 » » » } else { | |
| 389 » » » » q.order = append(q.order, col) | |
| 390 » » » } | |
| 391 » » }) | |
| 368 } | 392 } | 
| 369 | 393 | 
| 370 func (q *queryImpl) Project(fieldName ...string) ds.Query { | 394 func (q *queryImpl) Project(fieldName ...string) ds.Query { | 
| 371 » q = q.clone() | 395 » return q.checkMutateClone( | 
| 372 » q.project = append(q.project, fieldName...) | 396 » » func() error { | 
| 
dnj (Google)
2015/08/14 21:01:35
Test for duplicate project clauses, or is that coo
 
iannucci
2015/08/14 22:25:47
they're fine, they're just ignored. order doesn't
 | |
| 373 » return q | 397 » » » if q.keysOnly { | 
| 398 » » » » return errors.New("cannot project a keysOnly que ry") | |
| 399 » » » } | |
| 400 » » » for _, f := range fieldName { | |
| 401 » » » » if f == "" { | |
| 402 » » » » » return errors.New("cannot project on an empty field name") | |
| 403 » » » » } | |
| 404 » » » » if strings.HasPrefix(f, "__") && strings.HasSuff ix(f, "__") { | |
| 405 » » » » » return fmt.Errorf("cannot project on %q" , f) | |
| 406 » » » » } | |
| 407 » » » » if _, ok := q.eqFilters[f]; ok { | |
| 408 » » » » » return fmt.Errorf( | |
| 409 » » » » » » "cannot project on field which i s used in an equality filter: %q", f) | |
| 410 » » » » } | |
| 411 » » » } | |
| 412 » » » return nil | |
| 413 » » }, | |
| 414 » » func(q *queryImpl) { | |
| 415 » » » for _, f := range fieldName { | |
| 416 » » » » q.project[f] = struct{}{} | |
| 417 » » » } | |
| 418 » » }) | |
| 374 } | 419 } | 
| 375 | 420 | 
| 376 func (q *queryImpl) KeysOnly() ds.Query { | 421 func (q *queryImpl) KeysOnly() ds.Query { | 
| 377 » q = q.clone() | 422 » return q.checkMutateClone( | 
| 378 » q.keysOnly = true | 423 » » func() error { | 
| 379 » return q | 424 » » » if len(q.project) != 0 { | 
| 425 » » » » return errors.New("cannot project a keysOnly que ry") | |
| 426 » » » } | |
| 427 » » » return nil | |
| 428 » » }, | |
| 429 » » func(q *queryImpl) { | |
| 430 » » » q.keysOnly = true | |
| 431 » » }) | |
| 380 } | 432 } | 
| 381 | 433 | 
| 382 func (q *queryImpl) Limit(limit int) ds.Query { | 434 func (q *queryImpl) Limit(limit int) ds.Query { | 
| 383 » q = q.clone() | 435 » return q.checkMutateClone( | 
| 384 » if limit < math.MinInt32 || limit > math.MaxInt32 { | 436 » » func() error { | 
| 385 » » q.err = errors.New("datastore: query limit overflow") | 437 » » » if limit < math.MinInt32 || limit > math.MaxInt32 { | 
| 386 » » return q | 438 » » » » return errors.New("datastore: query limit overfl ow") | 
| 387 » } | 439 » » » } | 
| 388 » q.limit = int32(limit) | 440 » » » return nil | 
| 389 » return q | 441 » » }, | 
| 442 » » func(q *queryImpl) { | |
| 443 » » » q.limit = int32(limit) | |
| 444 » » }) | |
| 390 } | 445 } | 
| 391 | 446 | 
| 392 func (q *queryImpl) Offset(offset int) ds.Query { | 447 func (q *queryImpl) Offset(offset int) ds.Query { | 
| 393 » q = q.clone() | 448 » return q.checkMutateClone( | 
| 394 » if offset < 0 { | 449 » » func() error { | 
| 395 » » q.err = errors.New("datastore: negative query offset") | 450 » » » if offset < 0 { | 
| 396 » » return q | 451 » » » » return errors.New("datastore: negative query off set") | 
| 397 » } | 452 » » » } | 
| 398 » if offset > math.MaxInt32 { | 453 » » » if offset > math.MaxInt32 { | 
| 399 » » q.err = errors.New("datastore: query offset overflow") | 454 » » » » return errors.New("datastore: query offset overf low") | 
| 400 » » return q | 455 » » » } | 
| 401 » } | 456 » » » return nil | 
| 402 » q.offset = int32(offset) | 457 » » }, | 
| 403 » return q | 458 » » func(q *queryImpl) { | 
| 459 » » » q.offset = int32(offset) | |
| 460 » » }) | |
| 404 } | 461 } | 
| 405 | 462 | 
| 406 func (q *queryImpl) Start(c ds.Cursor) ds.Query { | 463 func (q *queryImpl) Start(c ds.Cursor) ds.Query { | 
| 407 » q = q.clone() | 464 » curs := queryCursor("") | 
| 408 » curs := c.(queryCursor) | 465 » return q.checkMutateClone( | 
| 409 » if !curs.Valid() { | 466 » » func() error { | 
| 410 » » q.err = errors.New("datastore: invalid cursor") | 467 » » » ok := false | 
| 411 » » return q | 468 » » » if curs, ok = c.(queryCursor); !ok { | 
| 412 » } | 469 » » » » return fmt.Errorf("start cursor is unknown type: %T", c) | 
| 413 » q.start = curs | 470 » » » } | 
| 414 » return q | 471 » » » if !curs.Valid() { | 
| 472 » » » » return errors.New("datastore: invalid cursor") | |
| 473 » » » } | |
| 474 » » » return nil | |
| 475 » » }, | |
| 476 » » func(q *queryImpl) { | |
| 477 » » » q.start = curs | |
| 478 » » }) | |
| 415 } | 479 } | 
| 416 | 480 | 
| 417 func (q *queryImpl) End(c ds.Cursor) ds.Query { | 481 func (q *queryImpl) End(c ds.Cursor) ds.Query { | 
| 418 » q = q.clone() | 482 » curs := queryCursor("") | 
| 419 » curs := c.(queryCursor) | 483 » return q.checkMutateClone( | 
| 420 » if !curs.Valid() { | 484 » » func() error { | 
| 421 » » q.err = errors.New("datastore: invalid cursor") | 485 » » » ok := false | 
| 422 » » return q | 486 » » » if curs, ok = c.(queryCursor); !ok { | 
| 423 » } | 487 » » » » return fmt.Errorf("end cursor is unknown type: % T", c) | 
| 424 » q.end = curs | 488 » » » } | 
| 425 » return q | 489 » » » if !curs.Valid() { | 
| 490 » » » » return errors.New("datastore: invalid cursor") | |
| 491 » » » } | |
| 492 » » » return nil | |
| 493 » » }, | |
| 494 » » func(q *queryImpl) { | |
| 495 » » » q.end = curs | |
| 496 » » }) | |
| 426 } | 497 } | 
| 427 | 498 | 
| 428 func (q *queryImpl) EventualConsistency() ds.Query { | 499 func (q *queryImpl) EventualConsistency() ds.Query { | 
| 429 » q = q.clone() | 500 » return q.checkMutateClone( | 
| 430 » q.eventualConsistency = true | 501 » » nil, func(q *queryImpl) { | 
| 431 » return q | 502 » » » q.eventualConsistency = true | 
| 432 } | 503 » » }) | 
| 504 } | |
| OLD | NEW |