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 "fmt" |
| 9 "testing" |
| 10 |
| 11 ds "github.com/luci/gae/service/datastore" |
| 12 "github.com/luci/gae/service/info" |
| 13 . "github.com/luci/luci-go/common/testing/assertions" |
| 14 . "github.com/smartystreets/goconvey/convey" |
| 15 "golang.org/x/net/context" |
| 16 ) |
| 17 |
| 18 type qExpect struct { |
| 19 q ds.Query |
| 20 inTxn bool |
| 21 |
| 22 get []ds.PropertyMap |
| 23 keys []ds.Key |
| 24 } |
| 25 |
| 26 type qExStage struct { |
| 27 addIdxs []*ds.IndexDefinition |
| 28 putEnts []ds.PropertyMap |
| 29 delEnts []ds.Key |
| 30 |
| 31 expect []qExpect |
| 32 |
| 33 extraFns []func(context.Context) |
| 34 } |
| 35 |
| 36 type qExTest struct { |
| 37 name string |
| 38 test []qExStage |
| 39 } |
| 40 |
| 41 var stage1Data = []ds.PropertyMap{ |
| 42 pmap("$key", key("Kind", 1), NEXT, |
| 43 "Val", 1, 2, 3, NEXT, |
| 44 "Extra", "hello", |
| 45 ), |
| 46 pmap("$key", key("Kind", 2), NEXT, |
| 47 "Val", 6, 8, 7, NEXT, |
| 48 "Extra", "zebra", |
| 49 ), |
| 50 pmap("$key", key("Kind", 3), NEXT, |
| 51 "Val", 1, 2, 2, 100, NEXT, |
| 52 "Extra", "waffle", |
| 53 ), |
| 54 pmap("$key", key("Kind", 6), NEXT, |
| 55 "Val", 5, NEXT, |
| 56 "Extra", "waffle", |
| 57 ), |
| 58 pmap("$key", key("Child", "seven", key("Kind", 3)), NEXT, |
| 59 "Interesting", 28, NEXT, |
| 60 "Extra", "hello", |
| 61 ), |
| 62 pmap("$key", key("Unique", 1), NEXT, |
| 63 "Derp", 39, |
| 64 ), |
| 65 } |
| 66 |
| 67 var stage2Data = []ds.PropertyMap{ |
| 68 pmap("$key", key("Kind", 1, key("Kind", 3)), NEXT, |
| 69 "Val", 2, 4, 28, NEXT, |
| 70 "Extra", "hello", "waffle", |
| 71 ), |
| 72 pmap("$key", key("Kind", 2, key("Kind", 3)), NEXT, |
| 73 "Val", 3, 4, NEXT, |
| 74 "Extra", "hello", "waffle", |
| 75 ), |
| 76 pmap("$key", key("Kind", 3, key("Kind", 3)), NEXT, |
| 77 "Val", 3, 4, 2, 1, NEXT, |
| 78 "Extra", "nuts", |
| 79 ), |
| 80 } |
| 81 |
| 82 var queryExecutionTests = []qExTest{ |
| 83 {"basic", []qExStage{ |
| 84 { |
| 85 addIdxs: []*ds.IndexDefinition{ |
| 86 indx("Unrelated", "-thing", "bob", "-__key__"), |
| 87 indx("Wat", "deep", "opt", "other"), |
| 88 indx("Wat", "meep", "opt", "other"), |
| 89 }, |
| 90 }, |
| 91 |
| 92 { |
| 93 expect: []qExpect{ |
| 94 // tests the case where the query has indexes to
fulfill it, but there |
| 95 // are no actual entities in the datastore. |
| 96 {q: nq("Wat").Filter("meep =", 1).Filter("deep =
", 2).Order("opt").Order("other"), |
| 97 get: []ds.PropertyMap{}}, |
| 98 }, |
| 99 }, |
| 100 |
| 101 { |
| 102 putEnts: stage1Data, |
| 103 expect: []qExpect{ |
| 104 {q: nq("Kind"), get: []ds.PropertyMap{}}, |
| 105 {q: nq("Child").Ancestor(key("Kind", 3)), keys:
[]ds.Key{ |
| 106 key("Child", "seven", key("Kind", 3)), |
| 107 }}, |
| 108 }, |
| 109 }, |
| 110 |
| 111 { |
| 112 putEnts: stage2Data, |
| 113 delEnts: []ds.Key{key("Unique", 1)}, |
| 114 addIdxs: []*ds.IndexDefinition{ |
| 115 indx("Kind!", "-Extra", "-Val"), |
| 116 indx("Kind!", "-Extra", "-Val", "-__key__"), |
| 117 indx("Kind!", "Bogus", "Extra", "-Val"), |
| 118 }, |
| 119 expect: []qExpect{ |
| 120 {q: nq("Kind"), get: stage1Data[:4]}, |
| 121 |
| 122 {q: nq("Kind").Offset(2).Limit(1), get: []ds.Pro
pertyMap{ |
| 123 stage1Data[2], |
| 124 }}, |
| 125 |
| 126 {q: nq("Missing"), get: []ds.PropertyMap{}}, |
| 127 |
| 128 {q: nq("Missing").Filter("Id <", 2).Filter("Id >
", 2), get: []ds.PropertyMap{}}, |
| 129 |
| 130 {q: nq("Missing").Filter("Bogus =", 3), get: []d
s.PropertyMap{}}, |
| 131 |
| 132 {q: nq("Kind").Filter("Extra =", "waffle"), get:
[]ds.PropertyMap{ |
| 133 stage1Data[2], stage1Data[3], |
| 134 }}, |
| 135 |
| 136 // get ziggy with it |
| 137 {q: nq("Kind").Filter("Extra =", "waffle").Filte
r("Val =", 100), get: []ds.PropertyMap{ |
| 138 stage1Data[2], |
| 139 }}, |
| 140 {q: nq("Child").Filter("Interesting =", 28).Filt
er("Extra =", "hello"), get: []ds.PropertyMap{ |
| 141 stage1Data[4], |
| 142 }}, |
| 143 |
| 144 {q: (nq("Kind").Ancestor(key("Kind", 3)).Order("
Val"). |
| 145 Start(curs("Val", 1, "__key__", key("Kin
d", 3))). |
| 146 End(curs("Val", 90, "__key__", key("Zeta
", "woot", key("Kind", 3))))), keys: []ds.Key{}, |
| 147 }, |
| 148 |
| 149 {q: nq("Kind").Filter("Val >", 2).Filter("Val <=
", 5), get: []ds.PropertyMap{ |
| 150 stage1Data[0], stage1Data[3], |
| 151 }}, |
| 152 |
| 153 {q: nq("Kind").Filter("Val >", 2).Filter("Val <=
", 5).Order("-Val"), get: []ds.PropertyMap{ |
| 154 stage1Data[3], stage1Data[0], |
| 155 }}, |
| 156 |
| 157 {q: nq("").Filter("__key__ >", key("Kind", 2)),
get: []ds.PropertyMap{ |
| 158 // TODO(riannucci): determine if the rea
l datastore shows metadata |
| 159 // during kindless queries. The document
ation seems to imply so, but |
| 160 // I'd like to be sure. |
| 161 pmap("$key", key("__entity_group__", 1,
key("Kind", 2)), NEXT, |
| 162 "__version__", 1), |
| 163 stage1Data[2], |
| 164 stage1Data[4], |
| 165 // this is 5 because the value is retrie
ved from HEAD and not from |
| 166 // the index snapshot! |
| 167 pmap("$key", key("__entity_group__", 1,
key("Kind", 3)), NEXT, |
| 168 "__version__", 5), |
| 169 stage1Data[3], |
| 170 pmap("$key", key("__entity_group__", 1,
key("Kind", 6)), NEXT, |
| 171 "__version__", 1), |
| 172 pmap("$key", key("__entity_group__", 1,
key("Unique", 1)), NEXT, |
| 173 "__version__", 2), |
| 174 }}, |
| 175 |
| 176 {q: (nq("Kind"). |
| 177 Filter("Val >", 2).Filter("Extra =", "wa
ffle"). |
| 178 Order("-Val"). |
| 179 Ancestor(key("Kind", 3))), |
| 180 get: []ds.PropertyMap{ |
| 181 stage1Data[2], |
| 182 stage2Data[0], |
| 183 stage2Data[1], |
| 184 }}, |
| 185 |
| 186 {q: (nq("Kind"). |
| 187 Filter("Val >", 2).Filter("Extra =", "wa
ffle"). |
| 188 Order("-Val").Order("-__key__"). |
| 189 Ancestor(key("Kind", 3))), |
| 190 get: []ds.PropertyMap{ |
| 191 stage1Data[2], |
| 192 stage2Data[0], |
| 193 stage2Data[1], |
| 194 }}, |
| 195 |
| 196 {q: (nq("Kind"). |
| 197 Filter("Val >", 2).Filter("Extra =", "wa
ffle"). |
| 198 Order("-Val"). |
| 199 Ancestor(key("Kind", 3)).Project("Val"))
, |
| 200 get: []ds.PropertyMap{ |
| 201 pmap("$key", key("Kind", 3), NEX
T, |
| 202 "Val", 100), |
| 203 pmap("$key", key("Kind", 1, key(
"Kind", 3)), NEXT, |
| 204 "Val", 28), |
| 205 pmap("$key", key("Kind", 1, key(
"Kind", 3)), NEXT, |
| 206 "Val", 4), |
| 207 pmap("$key", key("Kind", 2, key(
"Kind", 3)), NEXT, |
| 208 "Val", 4), |
| 209 pmap("$key", key("Kind", 2, key(
"Kind", 3)), NEXT, |
| 210 "Val", 3), |
| 211 }}, |
| 212 |
| 213 {q: (nq("Kind"). |
| 214 Filter("Val >", 2).Filter("Extra =", "wa
ffle"). |
| 215 Order("-Val"). |
| 216 Ancestor(key("Kind", 3)).Project("Val").
Distinct()), |
| 217 get: []ds.PropertyMap{ |
| 218 pmap("$key", key("Kind", 3), NEX
T, |
| 219 "Val", 100), |
| 220 pmap("$key", key("Kind", 1, key(
"Kind", 3)), NEXT, |
| 221 "Val", 28), |
| 222 pmap("$key", key("Kind", 1, key(
"Kind", 3)), NEXT, |
| 223 "Val", 4), |
| 224 pmap("$key", key("Kind", 2, key(
"Kind", 3)), NEXT, |
| 225 "Val", 3), |
| 226 }}, |
| 227 }, |
| 228 |
| 229 extraFns: []func(context.Context){ |
| 230 func(c context.Context) { |
| 231 data := ds.Get(c) |
| 232 curs := ds.Cursor(nil) |
| 233 |
| 234 q := nq("").Filter("__key__ >", key("Kin
d", 2)) |
| 235 |
| 236 err := data.Run(q, func(pm ds.PropertyMa
p, gc ds.CursorCB) bool { |
| 237 So(pm, ShouldResemble, pmap( |
| 238 "$key", key("__entity_gr
oup__", 1, key("Kind", 2)), NEXT, |
| 239 "__version__", 1)) |
| 240 |
| 241 err := error(nil) |
| 242 curs, err = gc() |
| 243 So(err, ShouldBeNil) |
| 244 return false |
| 245 }) |
| 246 So(err, ShouldBeNil) |
| 247 |
| 248 err = data.Run(q.Start(curs), func(pm ds
.PropertyMap, gc ds.CursorCB) bool { |
| 249 So(pm, ShouldResemble, stage1Dat
a[2]) |
| 250 return false |
| 251 }) |
| 252 So(err, ShouldBeNil) |
| 253 }, |
| 254 |
| 255 func(c context.Context) { |
| 256 data := ds.Get(c) |
| 257 q := nq("Something").Filter("Does =", 2)
.Order("Not").Order("Work") |
| 258 So(data.Run(q, func(ds.Key, ds.CursorCB)
bool { |
| 259 return true |
| 260 }), ShouldErrLike, "Try adding:\n C:Som
ething/Does/Not/Work") |
| 261 }, |
| 262 }, |
| 263 }, |
| 264 |
| 265 { |
| 266 expect: []qExpect{ |
| 267 // eventual consistency; Unique/1 is deleted at
HEAD. Keysonly finds it, |
| 268 // but 'normal' doesn't. |
| 269 {q: nq("Unique").Filter("__key__ >", key("AKind"
, 5)).Filter("__key__ <=", key("Zeta", "prime")), |
| 270 keys: []ds.Key{key("Unique", 1)}, |
| 271 get: []ds.PropertyMap{}}, |
| 272 |
| 273 {q: nq("Kind").Filter("Val =", 1).Filter("Val ="
, 3), get: []ds.PropertyMap{ |
| 274 stage1Data[0], stage2Data[2], |
| 275 }}, |
| 276 }, |
| 277 }, |
| 278 }}, |
| 279 } |
| 280 |
| 281 func TestQueryExecution(t *testing.T) { |
| 282 t.Parallel() |
| 283 |
| 284 Convey("Test query execution", t, func() { |
| 285 c, err := info.Get(Use(context.Background())).Namespace("ns") |
| 286 if err != nil { |
| 287 panic(err) |
| 288 } |
| 289 |
| 290 data := ds.Get(c) |
| 291 testing := data.Raw().Testable() |
| 292 |
| 293 for _, tc := range queryExecutionTests { |
| 294 Convey(tc.name, func() { |
| 295 for i, stage := range tc.test { |
| 296 // outside of Convey, since these must a
lways happen |
| 297 testing.CatchupIndexes() |
| 298 |
| 299 testing.AddIndexes(stage.addIdxs...) |
| 300 if err := data.PutMulti(stage.putEnts);
err != nil { |
| 301 // prevent Convey from thinking
this assertion should show up in |
| 302 // every test loop. |
| 303 panic(err) |
| 304 } |
| 305 |
| 306 if err := data.DeleteMulti(stage.delEnts
); err != nil { |
| 307 panic(err) |
| 308 } |
| 309 |
| 310 Convey(fmt.Sprintf("stage %d", i), func(
) { |
| 311 for j, expect := range stage.exp
ect { |
| 312 runner := func(f func(ic
context.Context) error, _ *ds.TransactionOptions) error { |
| 313 return f(c) |
| 314 } |
| 315 if expect.inTxn { |
| 316 runner = data.Ru
nInTransaction |
| 317 } |
| 318 |
| 319 if expect.keys != nil { |
| 320 runner(func(c co
ntext.Context) error { |
| 321 data :=
ds.Get(c) |
| 322 Convey(f
mt.Sprintf("expect %d (keys)", j), func() { |
| 323
rslt := []ds.Key(nil) |
| 324
So(data.GetAll(expect.q, &rslt), ShouldBeNil) |
| 325
So(len(rslt), ShouldEqual, len(expect.keys)) |
| 326
for i, r := range rslt { |
| 327
So(r, ShouldResemble, expect.keys[i]) |
| 328
} |
| 329 }) |
| 330 return n
il |
| 331 }, &ds.Transacti
onOptions{XG: true}) |
| 332 } |
| 333 |
| 334 if expect.get != nil { |
| 335 Convey(fmt.Sprin
tf("expect %d (data)", j), func() { |
| 336 runner(f
unc(c context.Context) error { |
| 337
rslt := []ds.PropertyMap(nil) |
| 338
So(data.GetAll(expect.q, &rslt), ShouldBeNil) |
| 339
So(len(rslt), ShouldEqual, len(expect.get)) |
| 340
for i, r := range rslt { |
| 341
So(r, ShouldResemble, expect.get[i]) |
| 342
} |
| 343
return nil |
| 344 }, &ds.T
ransactionOptions{XG: true}) |
| 345 }) |
| 346 } |
| 347 } |
| 348 |
| 349 for j, fn := range stage.extraFn
s { |
| 350 Convey(fmt.Sprintf("extr
aFn %d", j), func() { |
| 351 fn(c) |
| 352 }) |
| 353 } |
| 354 }) |
| 355 } |
| 356 }) |
| 357 } |
| 358 }) |
| 359 } |
OLD | NEW |