| Index: service/datastore/query_test.go
|
| diff --git a/service/datastore/query_test.go b/service/datastore/query_test.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c4c71ccca2fb13e15e15001f9cf2879b63dd1580
|
| --- /dev/null
|
| +++ b/service/datastore/query_test.go
|
| @@ -0,0 +1,291 @@
|
| +// 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 datastore
|
| +
|
| +import (
|
| + "math"
|
| + "testing"
|
| +
|
| + . "github.com/luci/luci-go/common/testing/assertions"
|
| + . "github.com/smartystreets/goconvey/convey"
|
| +)
|
| +
|
| +const (
|
| + MaxUint = ^uint(0)
|
| + MaxInt = int(MaxUint >> 1)
|
| + IntIs32Bits = int64(MaxInt) < math.MaxInt64
|
| +)
|
| +
|
| +func TestDatastoreQueries(t *testing.T) {
|
| + Convey("Datastore Query suport", t, func() {
|
| + Convey("can create good queries", func() {
|
| + q := NewQuery("Foo").Gt("farnsworth", 20).KeysOnly(true).Limit(10).Offset(39)
|
| +
|
| + start := fakeCursor("hi")
|
| +
|
| + end := fakeCursor("end")
|
| +
|
| + q = q.Start(start).End(end)
|
| + So(q, ShouldNotBeNil)
|
| + fq, err := q.Finalize()
|
| + So(fq, ShouldNotBeNil)
|
| + So(err, ShouldBeNil)
|
| + })
|
| +
|
| + Convey("ensures orders make sense", func() {
|
| + q := NewQuery("Cool")
|
| + q = q.Eq("cat", 19).Eq("bob", 10).Order("bob", "bob")
|
| +
|
| + Convey("removes dups and equality orders", func() {
|
| + q = q.Order("wat")
|
| + fq, err := q.Finalize()
|
| + So(err, ShouldBeNil)
|
| + So(fq.Orders(), ShouldResemble, []IndexColumn{
|
| + {Property: "wat"}, {Property: "__key__"}})
|
| + })
|
| + })
|
| +
|
| + })
|
| +}
|
| +
|
| +type queryTest struct {
|
| + // name is the name of the test case
|
| + name string
|
| +
|
| + // q is the input query
|
| + q *Query
|
| +
|
| + // gql is the expected generated GQL.
|
| + gql string
|
| +
|
| + // err is the error to expect after prepping the query (error, string or nil)
|
| + err interface{}
|
| +
|
| + // equivalentQuery is another query which ShouldResemble q. This is useful to
|
| + // see the effects of redundancy pruning on e.g. filters.
|
| + equivalentQuery *Query
|
| +}
|
| +
|
| +type sillyCursor string
|
| +
|
| +func (s sillyCursor) String() string { return string(s) }
|
| +
|
| +func nq(kinds ...string) *Query {
|
| + kind := "Foo"
|
| + if len(kinds) > 0 {
|
| + kind = kinds[0]
|
| + }
|
| + return NewQuery(kind)
|
| +}
|
| +
|
| +func mkKey(elems ...interface{}) *Key {
|
| + return MakeKey("s~aid", "ns", elems...)
|
| +}
|
| +
|
| +var queryTests = []queryTest{
|
| + {"only one inequality",
|
| + nq().Order("bob", "wat").Gt("bob", 10).Lt("wat", 29),
|
| + "",
|
| + "inequality filters on multiple properties", nil},
|
| +
|
| + {"bad order",
|
| + nq().Order("+Bob"),
|
| + "",
|
| + "invalid order", nil},
|
| +
|
| + {"empty order",
|
| + nq().Order(""),
|
| + "",
|
| + "empty order", nil},
|
| +
|
| + {"negative offset disables Offset",
|
| + nq().Offset(100).Offset(-20),
|
| + "SELECT * FROM `Foo` ORDER BY `__key__`",
|
| + nil, nq()},
|
| +
|
| + {"projecting a keys-only query",
|
| + nq().Project("hello").KeysOnly(true),
|
| + "",
|
| + "cannot project a keysOnly query", nil},
|
| +
|
| + {"projecting a keys-only query (reverse)",
|
| + nq().KeysOnly(true).Project("hello"),
|
| + "",
|
| + "cannot project a keysOnly query", nil},
|
| +
|
| + {"projecting an empty field",
|
| + nq().Project("hello", ""),
|
| + "",
|
| + "cannot filter/project on: \"\"", nil},
|
| +
|
| + {"projecting __key__",
|
| + nq().Project("hello", "__key__"),
|
| + "",
|
| + "cannot project on \"__key__\"", nil},
|
| +
|
| + {"projecting a duplicate",
|
| + nq().Project("hello", "hello"),
|
| + "SELECT `hello` FROM `Foo` ORDER BY `hello`, `__key__`",
|
| + nil, nq().Project("hello")},
|
| +
|
| + {"projecting a duplicate (style 2)",
|
| + nq().Project("hello").Project("hello"),
|
| + "SELECT `hello` FROM `Foo` ORDER BY `hello`, `__key__`",
|
| + nil, nq().Project("hello")},
|
| +
|
| + {"bad ancestors",
|
| + nq().Ancestor(mkKey("goop", 0)),
|
| + "",
|
| + ErrInvalidKey, nil},
|
| +
|
| + {"nil ancestors",
|
| + nq().Ancestor(nil),
|
| + "SELECT * FROM `Foo` ORDER BY `__key__`",
|
| + nil, nq()},
|
| +
|
| + {"Bad key filters",
|
| + nq().Gt("__key__", mkKey("goop", 0)),
|
| + "",
|
| + ErrInvalidKey, nil},
|
| +
|
| + {"filters for __key__ that aren't keys",
|
| + nq().Gt("__key__", 10),
|
| + "",
|
| + "filters on \"__key__\" must have type *Key", nil},
|
| +
|
| + {"multiple inequalities",
|
| + nq().Gt("bob", 19).Lt("charlie", 20),
|
| + "",
|
| + "inequality filters on multiple properties", nil},
|
| +
|
| + {"inequality must be first sort order",
|
| + nq().Gt("bob", 19).Order("-charlie"),
|
| + "",
|
| + "first sort order", nil},
|
| +
|
| + {"inequality must be first sort order (reverse)",
|
| + nq().Order("-charlie").Gt("bob", 19),
|
| + "",
|
| + "first sort order", nil},
|
| +
|
| + {"equality filter projected field",
|
| + nq().Project("foo").Eq("foo", 10),
|
| + "",
|
| + "cannot project", nil},
|
| +
|
| + {"equality filter projected field (reverse)",
|
| + nq().Eq("foo", 10).Project("foo"),
|
| + "",
|
| + "cannot project", nil},
|
| +
|
| + {"kindless with non-__key__ filters",
|
| + nq("").Lt("face", 25.3),
|
| + "",
|
| + "kindless queries can only filter on __key__", nil},
|
| +
|
| + {"kindless with non-__key__ orders",
|
| + nq("").Order("face"),
|
| + "",
|
| + "invalid order for kindless query", nil},
|
| +
|
| + {"kindless with descending-__key__ order",
|
| + nq("").Order("-__key__"),
|
| + "",
|
| + "invalid order for kindless query", nil},
|
| +
|
| + {"distinct non-projection",
|
| + nq().Distinct(true).Gt("marla", 1),
|
| + "SELECT * FROM `Foo` WHERE `marla` > 1 ORDER BY `marla`, `__key__`",
|
| + nil, nq().Gt("marla", 1)},
|
| +
|
| + {"chained errors return the first",
|
| + nq().Eq("__reserved__", 100).Eq("hello", "wurld").Order(""),
|
| + "",
|
| + "__reserved__", nil},
|
| +
|
| + {"multiple ancestors",
|
| + nq().Ancestor(mkKey("something", "correct")).Ancestor(mkKey("something", "else")),
|
| + ("SELECT * FROM `Foo` " +
|
| + "WHERE __key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"something\", \"else\") " +
|
| + "ORDER BY `__key__`"),
|
| + nil, nq().Ancestor(mkKey("something", "else"))},
|
| +
|
| + {"filter with illegal type",
|
| + nq().Eq("something", complex(1, 2)),
|
| + "",
|
| + "bad type complex", nil},
|
| +
|
| + {"sort orders used for equality are ignored",
|
| + nq().Order("a", "b", "c").Eq("b", 2),
|
| + "SELECT * FROM `Foo` WHERE `b` = 2 ORDER BY `a`, `c`, `__key__`",
|
| + nil, nq().Order("a", "c").Eq("b", 2)},
|
| +
|
| + {"sort orders used for equality are ignored (reversed)",
|
| + nq().Eq("b", 2).Order("a", "b", "c"),
|
| + "SELECT * FROM `Foo` WHERE `b` = 2 ORDER BY `a`, `c`, `__key__`",
|
| + nil,
|
| + nq().Order("a", "c").Eq("b", 2)},
|
| +
|
| + {"duplicate orders are ignored",
|
| + nq().Order("a").Order("a").Order("a"),
|
| + "SELECT * FROM `Foo` ORDER BY `a`, `__key__`",
|
| + nil,
|
| + nq().Order("a")},
|
| +
|
| + {"Filtering on a reserved property is forbidden",
|
| + nq().Gte("__special__", 10),
|
| + "",
|
| + "cannot filter/project on reserved property: \"__special__\"",
|
| + nil},
|
| +
|
| + {"in-bound key filters with ancestor OK",
|
| + nq().Ancestor(mkKey("Hello", 10)).Lt("__key__", mkKey("Hello", 10, "Something", "hi")),
|
| + ("SELECT * FROM `Foo` " +
|
| + "WHERE `__key__` < KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Hello\", 10, \"Something\", \"hi\") AND " +
|
| + "__key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Hello\", 10) " +
|
| + "ORDER BY `__key__`"),
|
| + nil,
|
| + nil},
|
| +
|
| + {"projection elements get filled in",
|
| + nq().Project("Foo", "Bar").Order("-Bar"),
|
| + "SELECT `Bar`, `Foo` FROM `Foo` ORDER BY `Bar` DESC, `Foo`, `__key__`",
|
| + nil, nq().Project("Foo", "Bar").Order("-Bar").Order("Foo")},
|
| +
|
| + {"query without anything is fine",
|
| + nq(),
|
| + "SELECT * FROM `Foo` ORDER BY `__key__`",
|
| + nil,
|
| + nil},
|
| +}
|
| +
|
| +func TestQueries(t *testing.T) {
|
| + t.Parallel()
|
| +
|
| + Convey("queries have tons of condition checking", t, func() {
|
| + for _, tc := range queryTests {
|
| + Convey(tc.name, func() {
|
| + fq, err := tc.q.Finalize()
|
| + if err == nil {
|
| + err = fq.Valid("s~aid", "ns")
|
| + }
|
| + So(err, ShouldErrLike, tc.err)
|
| +
|
| + if tc.gql != "" {
|
| + So(fq.GQL(), ShouldEqual, tc.gql)
|
| + }
|
| +
|
| + if tc.equivalentQuery != nil {
|
| + fq2, err := tc.equivalentQuery.Finalize()
|
| + So(err, ShouldBeNil)
|
| +
|
| + fq.original = nil
|
| + fq2.original = nil
|
| + So(fq, ShouldResemble, fq2)
|
| + }
|
| + })
|
| + }
|
| + })
|
| +}
|
|
|