| Index: impl/memory/datastore_query_execution_test.go
|
| diff --git a/impl/memory/datastore_query_execution_test.go b/impl/memory/datastore_query_execution_test.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..01e779d2335ead31c50a4d907bb22234d311ae86
|
| --- /dev/null
|
| +++ b/impl/memory/datastore_query_execution_test.go
|
| @@ -0,0 +1,359 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package memory
|
| +
|
| +import (
|
| + "fmt"
|
| + "testing"
|
| +
|
| + ds "github.com/luci/gae/service/datastore"
|
| + "github.com/luci/gae/service/info"
|
| + . "github.com/luci/luci-go/common/testing/assertions"
|
| + . "github.com/smartystreets/goconvey/convey"
|
| + "golang.org/x/net/context"
|
| +)
|
| +
|
| +type qExpect struct {
|
| + q ds.Query
|
| + inTxn bool
|
| +
|
| + get []ds.PropertyMap
|
| + keys []ds.Key
|
| +}
|
| +
|
| +type qExStage struct {
|
| + addIdxs []*ds.IndexDefinition
|
| + putEnts []ds.PropertyMap
|
| + delEnts []ds.Key
|
| +
|
| + expect []qExpect
|
| +
|
| + extraFns []func(context.Context)
|
| +}
|
| +
|
| +type qExTest struct {
|
| + name string
|
| + test []qExStage
|
| +}
|
| +
|
| +var stage1Data = []ds.PropertyMap{
|
| + pmap("$key", key("Kind", 1), NEXT,
|
| + "Val", 1, 2, 3, NEXT,
|
| + "Extra", "hello",
|
| + ),
|
| + pmap("$key", key("Kind", 2), NEXT,
|
| + "Val", 6, 8, 7, NEXT,
|
| + "Extra", "zebra",
|
| + ),
|
| + pmap("$key", key("Kind", 3), NEXT,
|
| + "Val", 1, 2, 2, 100, NEXT,
|
| + "Extra", "waffle",
|
| + ),
|
| + pmap("$key", key("Kind", 6), NEXT,
|
| + "Val", 5, NEXT,
|
| + "Extra", "waffle",
|
| + ),
|
| + pmap("$key", key("Child", "seven", key("Kind", 3)), NEXT,
|
| + "Interesting", 28, NEXT,
|
| + "Extra", "hello",
|
| + ),
|
| + pmap("$key", key("Unique", 1), NEXT,
|
| + "Derp", 39,
|
| + ),
|
| +}
|
| +
|
| +var stage2Data = []ds.PropertyMap{
|
| + pmap("$key", key("Kind", 1, key("Kind", 3)), NEXT,
|
| + "Val", 2, 4, 28, NEXT,
|
| + "Extra", "hello", "waffle",
|
| + ),
|
| + pmap("$key", key("Kind", 2, key("Kind", 3)), NEXT,
|
| + "Val", 3, 4, NEXT,
|
| + "Extra", "hello", "waffle",
|
| + ),
|
| + pmap("$key", key("Kind", 3, key("Kind", 3)), NEXT,
|
| + "Val", 3, 4, 2, 1, NEXT,
|
| + "Extra", "nuts",
|
| + ),
|
| +}
|
| +
|
| +var queryExecutionTests = []qExTest{
|
| + {"basic", []qExStage{
|
| + {
|
| + addIdxs: []*ds.IndexDefinition{
|
| + indx("Unrelated", "-thing", "bob", "-__key__"),
|
| + indx("Wat", "deep", "opt", "other"),
|
| + indx("Wat", "meep", "opt", "other"),
|
| + },
|
| + },
|
| +
|
| + {
|
| + expect: []qExpect{
|
| + // tests the case where the query has indexes to fulfill it, but there
|
| + // are no actual entities in the datastore.
|
| + {q: nq("Wat").Filter("meep =", 1).Filter("deep =", 2).Order("opt").Order("other"),
|
| + get: []ds.PropertyMap{}},
|
| + },
|
| + },
|
| +
|
| + {
|
| + putEnts: stage1Data,
|
| + expect: []qExpect{
|
| + {q: nq("Kind"), get: []ds.PropertyMap{}},
|
| + {q: nq("Child").Ancestor(key("Kind", 3)), keys: []ds.Key{
|
| + key("Child", "seven", key("Kind", 3)),
|
| + }},
|
| + },
|
| + },
|
| +
|
| + {
|
| + putEnts: stage2Data,
|
| + delEnts: []ds.Key{key("Unique", 1)},
|
| + addIdxs: []*ds.IndexDefinition{
|
| + indx("Kind!", "-Extra", "-Val"),
|
| + indx("Kind!", "-Extra", "-Val", "-__key__"),
|
| + indx("Kind!", "Bogus", "Extra", "-Val"),
|
| + },
|
| + expect: []qExpect{
|
| + {q: nq("Kind"), get: stage1Data[:4]},
|
| +
|
| + {q: nq("Kind").Offset(2).Limit(1), get: []ds.PropertyMap{
|
| + stage1Data[2],
|
| + }},
|
| +
|
| + {q: nq("Missing"), get: []ds.PropertyMap{}},
|
| +
|
| + {q: nq("Missing").Filter("Id <", 2).Filter("Id >", 2), get: []ds.PropertyMap{}},
|
| +
|
| + {q: nq("Missing").Filter("Bogus =", 3), get: []ds.PropertyMap{}},
|
| +
|
| + {q: nq("Kind").Filter("Extra =", "waffle"), get: []ds.PropertyMap{
|
| + stage1Data[2], stage1Data[3],
|
| + }},
|
| +
|
| + // get ziggy with it
|
| + {q: nq("Kind").Filter("Extra =", "waffle").Filter("Val =", 100), get: []ds.PropertyMap{
|
| + stage1Data[2],
|
| + }},
|
| + {q: nq("Child").Filter("Interesting =", 28).Filter("Extra =", "hello"), get: []ds.PropertyMap{
|
| + stage1Data[4],
|
| + }},
|
| +
|
| + {q: (nq("Kind").Ancestor(key("Kind", 3)).Order("Val").
|
| + Start(curs("Val", 1, "__key__", key("Kind", 3))).
|
| + End(curs("Val", 90, "__key__", key("Zeta", "woot", key("Kind", 3))))), keys: []ds.Key{},
|
| + },
|
| +
|
| + {q: nq("Kind").Filter("Val >", 2).Filter("Val <=", 5), get: []ds.PropertyMap{
|
| + stage1Data[0], stage1Data[3],
|
| + }},
|
| +
|
| + {q: nq("Kind").Filter("Val >", 2).Filter("Val <=", 5).Order("-Val"), get: []ds.PropertyMap{
|
| + stage1Data[3], stage1Data[0],
|
| + }},
|
| +
|
| + {q: nq("").Filter("__key__ >", key("Kind", 2)), get: []ds.PropertyMap{
|
| + // TODO(riannucci): determine if the real datastore shows metadata
|
| + // during kindless queries. The documentation seems to imply so, but
|
| + // I'd like to be sure.
|
| + pmap("$key", key("__entity_group__", 1, key("Kind", 2)), NEXT,
|
| + "__version__", 1),
|
| + stage1Data[2],
|
| + stage1Data[4],
|
| + // this is 5 because the value is retrieved from HEAD and not from
|
| + // the index snapshot!
|
| + pmap("$key", key("__entity_group__", 1, key("Kind", 3)), NEXT,
|
| + "__version__", 5),
|
| + stage1Data[3],
|
| + pmap("$key", key("__entity_group__", 1, key("Kind", 6)), NEXT,
|
| + "__version__", 1),
|
| + pmap("$key", key("__entity_group__", 1, key("Unique", 1)), NEXT,
|
| + "__version__", 2),
|
| + }},
|
| +
|
| + {q: (nq("Kind").
|
| + Filter("Val >", 2).Filter("Extra =", "waffle").
|
| + Order("-Val").
|
| + Ancestor(key("Kind", 3))),
|
| + get: []ds.PropertyMap{
|
| + stage1Data[2],
|
| + stage2Data[0],
|
| + stage2Data[1],
|
| + }},
|
| +
|
| + {q: (nq("Kind").
|
| + Filter("Val >", 2).Filter("Extra =", "waffle").
|
| + Order("-Val").Order("-__key__").
|
| + Ancestor(key("Kind", 3))),
|
| + get: []ds.PropertyMap{
|
| + stage1Data[2],
|
| + stage2Data[0],
|
| + stage2Data[1],
|
| + }},
|
| +
|
| + {q: (nq("Kind").
|
| + Filter("Val >", 2).Filter("Extra =", "waffle").
|
| + Order("-Val").
|
| + Ancestor(key("Kind", 3)).Project("Val")),
|
| + get: []ds.PropertyMap{
|
| + pmap("$key", key("Kind", 3), NEXT,
|
| + "Val", 100),
|
| + pmap("$key", key("Kind", 1, key("Kind", 3)), NEXT,
|
| + "Val", 28),
|
| + pmap("$key", key("Kind", 1, key("Kind", 3)), NEXT,
|
| + "Val", 4),
|
| + pmap("$key", key("Kind", 2, key("Kind", 3)), NEXT,
|
| + "Val", 4),
|
| + pmap("$key", key("Kind", 2, key("Kind", 3)), NEXT,
|
| + "Val", 3),
|
| + }},
|
| +
|
| + {q: (nq("Kind").
|
| + Filter("Val >", 2).Filter("Extra =", "waffle").
|
| + Order("-Val").
|
| + Ancestor(key("Kind", 3)).Project("Val").Distinct()),
|
| + get: []ds.PropertyMap{
|
| + pmap("$key", key("Kind", 3), NEXT,
|
| + "Val", 100),
|
| + pmap("$key", key("Kind", 1, key("Kind", 3)), NEXT,
|
| + "Val", 28),
|
| + pmap("$key", key("Kind", 1, key("Kind", 3)), NEXT,
|
| + "Val", 4),
|
| + pmap("$key", key("Kind", 2, key("Kind", 3)), NEXT,
|
| + "Val", 3),
|
| + }},
|
| + },
|
| +
|
| + extraFns: []func(context.Context){
|
| + func(c context.Context) {
|
| + data := ds.Get(c)
|
| + curs := ds.Cursor(nil)
|
| +
|
| + q := nq("").Filter("__key__ >", key("Kind", 2))
|
| +
|
| + err := data.Run(q, func(pm ds.PropertyMap, gc ds.CursorCB) bool {
|
| + So(pm, ShouldResemble, pmap(
|
| + "$key", key("__entity_group__", 1, key("Kind", 2)), NEXT,
|
| + "__version__", 1))
|
| +
|
| + err := error(nil)
|
| + curs, err = gc()
|
| + So(err, ShouldBeNil)
|
| + return false
|
| + })
|
| + So(err, ShouldBeNil)
|
| +
|
| + err = data.Run(q.Start(curs), func(pm ds.PropertyMap, gc ds.CursorCB) bool {
|
| + So(pm, ShouldResemble, stage1Data[2])
|
| + return false
|
| + })
|
| + So(err, ShouldBeNil)
|
| + },
|
| +
|
| + func(c context.Context) {
|
| + data := ds.Get(c)
|
| + q := nq("Something").Filter("Does =", 2).Order("Not").Order("Work")
|
| + So(data.Run(q, func(ds.Key, ds.CursorCB) bool {
|
| + return true
|
| + }), ShouldErrLike, "Try adding:\n C:Something/Does/Not/Work")
|
| + },
|
| + },
|
| + },
|
| +
|
| + {
|
| + expect: []qExpect{
|
| + // eventual consistency; Unique/1 is deleted at HEAD. Keysonly finds it,
|
| + // but 'normal' doesn't.
|
| + {q: nq("Unique").Filter("__key__ >", key("AKind", 5)).Filter("__key__ <=", key("Zeta", "prime")),
|
| + keys: []ds.Key{key("Unique", 1)},
|
| + get: []ds.PropertyMap{}},
|
| +
|
| + {q: nq("Kind").Filter("Val =", 1).Filter("Val =", 3), get: []ds.PropertyMap{
|
| + stage1Data[0], stage2Data[2],
|
| + }},
|
| + },
|
| + },
|
| + }},
|
| +}
|
| +
|
| +func TestQueryExecution(t *testing.T) {
|
| + t.Parallel()
|
| +
|
| + Convey("Test query execution", t, func() {
|
| + c, err := info.Get(Use(context.Background())).Namespace("ns")
|
| + if err != nil {
|
| + panic(err)
|
| + }
|
| +
|
| + data := ds.Get(c)
|
| + testing := data.Raw().Testable()
|
| +
|
| + for _, tc := range queryExecutionTests {
|
| + Convey(tc.name, func() {
|
| + for i, stage := range tc.test {
|
| + // outside of Convey, since these must always happen
|
| + testing.CatchupIndexes()
|
| +
|
| + testing.AddIndexes(stage.addIdxs...)
|
| + if err := data.PutMulti(stage.putEnts); err != nil {
|
| + // prevent Convey from thinking this assertion should show up in
|
| + // every test loop.
|
| + panic(err)
|
| + }
|
| +
|
| + if err := data.DeleteMulti(stage.delEnts); err != nil {
|
| + panic(err)
|
| + }
|
| +
|
| + Convey(fmt.Sprintf("stage %d", i), func() {
|
| + for j, expect := range stage.expect {
|
| + runner := func(f func(ic context.Context) error, _ *ds.TransactionOptions) error {
|
| + return f(c)
|
| + }
|
| + if expect.inTxn {
|
| + runner = data.RunInTransaction
|
| + }
|
| +
|
| + if expect.keys != nil {
|
| + runner(func(c context.Context) error {
|
| + data := ds.Get(c)
|
| + Convey(fmt.Sprintf("expect %d (keys)", j), func() {
|
| + rslt := []ds.Key(nil)
|
| + So(data.GetAll(expect.q, &rslt), ShouldBeNil)
|
| + So(len(rslt), ShouldEqual, len(expect.keys))
|
| + for i, r := range rslt {
|
| + So(r, ShouldResemble, expect.keys[i])
|
| + }
|
| + })
|
| + return nil
|
| + }, &ds.TransactionOptions{XG: true})
|
| + }
|
| +
|
| + if expect.get != nil {
|
| + Convey(fmt.Sprintf("expect %d (data)", j), func() {
|
| + runner(func(c context.Context) error {
|
| + rslt := []ds.PropertyMap(nil)
|
| + So(data.GetAll(expect.q, &rslt), ShouldBeNil)
|
| + So(len(rslt), ShouldEqual, len(expect.get))
|
| + for i, r := range rslt {
|
| + So(r, ShouldResemble, expect.get[i])
|
| + }
|
| + return nil
|
| + }, &ds.TransactionOptions{XG: true})
|
| + })
|
| + }
|
| + }
|
| +
|
| + for j, fn := range stage.extraFns {
|
| + Convey(fmt.Sprintf("extraFn %d", j), func() {
|
| + fn(c)
|
| + })
|
| + }
|
| + })
|
| + }
|
| + })
|
| + }
|
| + })
|
| +}
|
|
|