go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/query_test.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package datastore
    16  
    17  import (
    18  	"math"
    19  	"testing"
    20  
    21  	"go.chromium.org/luci/common/sync/parallel"
    22  
    23  	. "github.com/smartystreets/goconvey/convey"
    24  	. "go.chromium.org/luci/common/testing/assertions"
    25  )
    26  
    27  const (
    28  	MaxUint     = ^uint(0)
    29  	MaxInt      = int(MaxUint >> 1)
    30  	IntIs32Bits = int64(MaxInt) < math.MaxInt64
    31  )
    32  
    33  func TestDatastoreQueries(t *testing.T) {
    34  	Convey("Datastore Query suport", t, func() {
    35  		Convey("can create good queries", func() {
    36  			q := NewQuery("Foo").Gt("farnsworth", 20).KeysOnly(true).Limit(10).Offset(39)
    37  
    38  			start := fakeCursor(1337)
    39  
    40  			end := fakeCursor(24601)
    41  
    42  			q = q.Start(start).End(end)
    43  			So(q, ShouldNotBeNil)
    44  			fq, err := q.Finalize()
    45  			So(fq, ShouldNotBeNil)
    46  			So(err, ShouldBeNil)
    47  		})
    48  
    49  		Convey("ensures orders make sense", func() {
    50  			q := NewQuery("Cool")
    51  			q = q.Eq("cat", 19).Eq("bob", 10).Order("bob", "bob")
    52  
    53  			Convey("removes dups and equality orders", func() {
    54  				q = q.Order("wat")
    55  				fq, err := q.Finalize()
    56  				So(err, ShouldBeNil)
    57  				So(fq.Orders(), ShouldResemble, []IndexColumn{
    58  					{Property: "wat"}, {Property: "__key__"}})
    59  			})
    60  		})
    61  
    62  	})
    63  }
    64  
    65  func TestDatastoreQueriesLess(t *testing.T) {
    66  	Convey("Datastore Query ordering", t, func() {
    67  		Convey("Compare kind", func() {
    68  			q := NewQuery("Foo")
    69  			q1 := NewQuery("Foo1")
    70  
    71  			So(q.Less(q1), ShouldBeTrue)
    72  			So(q1.Less(q), ShouldBeFalse)
    73  
    74  			q2 := NewQuery("Foo")
    75  
    76  			So(q.Less(q2), ShouldBeFalse)
    77  			So(q2.Less(q), ShouldBeFalse)
    78  		})
    79  		Convey("Compare firestore mode", func() {
    80  			q := NewQuery("Foo")
    81  			q1 := NewQuery("Foo").FirestoreMode(true)
    82  
    83  			So(q.Less(q1), ShouldBeTrue)
    84  			So(q1.Less(q), ShouldBeFalse)
    85  
    86  			q = q.FirestoreMode(true)
    87  
    88  			So(q.Less(q1), ShouldBeFalse)
    89  			So(q1.Less(q), ShouldBeFalse)
    90  		})
    91  		Convey("Compare eventual consistency", func() {
    92  			q := NewQuery("Foo")
    93  			q1 := NewQuery("Foo").EventualConsistency(true)
    94  
    95  			So(q.Less(q1), ShouldBeTrue)
    96  			So(q1.Less(q), ShouldBeFalse)
    97  
    98  			q = q.EventualConsistency(true)
    99  
   100  			So(q.Less(q1), ShouldBeFalse)
   101  			So(q1.Less(q), ShouldBeFalse)
   102  		})
   103  		Convey("Compare keys only", func() {
   104  			q := NewQuery("Foo")
   105  			q1 := NewQuery("Foo").KeysOnly(true)
   106  
   107  			So(q.Less(q1), ShouldBeTrue)
   108  			So(q1.Less(q), ShouldBeFalse)
   109  
   110  			q = q.KeysOnly(true)
   111  
   112  			So(q.Less(q1), ShouldBeFalse)
   113  			So(q1.Less(q), ShouldBeFalse)
   114  		})
   115  		Convey("Compare distinct", func() {
   116  			q := NewQuery("Foo")
   117  			q1 := NewQuery("Foo").Distinct(true)
   118  
   119  			So(q.Less(q1), ShouldBeTrue)
   120  			So(q1.Less(q), ShouldBeFalse)
   121  
   122  			q = q.Distinct(true)
   123  
   124  			So(q.Less(q1), ShouldBeFalse)
   125  			So(q1.Less(q), ShouldBeFalse)
   126  		})
   127  		Convey("Compare limit", func() {
   128  			q := NewQuery("Foo")
   129  			q1 := NewQuery("Foo").Limit(20)
   130  
   131  			So(q.Less(q1), ShouldBeTrue)
   132  			So(q1.Less(q), ShouldBeFalse)
   133  
   134  			q = q.Limit(20)
   135  
   136  			So(q.Less(q1), ShouldBeFalse)
   137  			So(q1.Less(q), ShouldBeFalse)
   138  		})
   139  		Convey("Compare offset", func() {
   140  			q := NewQuery("Foo")
   141  			q1 := NewQuery("Foo").Offset(20)
   142  
   143  			So(q.Less(q1), ShouldBeTrue)
   144  			So(q1.Less(q), ShouldBeFalse)
   145  
   146  			q = q.Offset(20)
   147  
   148  			So(q.Less(q1), ShouldBeFalse)
   149  			So(q1.Less(q), ShouldBeFalse)
   150  		})
   151  		Convey("Compare order", func() {
   152  			q := NewQuery("Foo")
   153  			q1 := NewQuery("Foo").Order("conrad")
   154  
   155  			So(q.Less(q1), ShouldBeTrue)
   156  			So(q1.Less(q), ShouldBeFalse)
   157  
   158  			q = q.Order("turanga")
   159  
   160  			So(q.Less(q1), ShouldBeFalse)
   161  			So(q1.Less(q), ShouldBeTrue)
   162  
   163  			q = q.ClearOrder().Order("conrad", "turanga")
   164  
   165  			So(q.Less(q1), ShouldBeFalse)
   166  			So(q1.Less(q), ShouldBeTrue)
   167  		})
   168  		Convey("Compare projection", func() {
   169  			q := NewQuery("Foo")
   170  			q1 := NewQuery("Foo").Project("conrad")
   171  
   172  			// [] vs ["conrad"]
   173  			So(q.Less(q1), ShouldBeTrue)
   174  			So(q1.Less(q), ShouldBeFalse)
   175  
   176  			q = q.Project("turanga")
   177  
   178  			// ["turanga"] vs ["conrad"]
   179  			So(q.Less(q1), ShouldBeFalse)
   180  			So(q1.Less(q), ShouldBeTrue)
   181  
   182  			q1 = q1.Project("turanga")
   183  
   184  			// ["turanga"] vs ["conrad", "turanga"]
   185  			So(q.Less(q1), ShouldBeTrue)
   186  			So(q1.Less(q), ShouldBeFalse)
   187  
   188  			q = q.Project("zoidberg")
   189  
   190  			// ["turanga", "zoidberg"] vs ["conrad", "turanga"]
   191  			So(q.Less(q1), ShouldBeFalse)
   192  			So(q1.Less(q), ShouldBeTrue)
   193  		})
   194  		Convey("Compare equality", func() {
   195  			// "... turanga == 10 ..." compare "... turanga == 24 ..."
   196  			q := NewQuery("Foo").Eq("turanga", "10")
   197  			q1 := NewQuery("Foo").Eq("turanga", "24")
   198  
   199  			So(q.Less(q1), ShouldBeTrue)
   200  			So(q1.Less(q), ShouldBeFalse)
   201  
   202  			// "... turanga == 10 ..." compare "... turanga == 10 ..."
   203  			q = NewQuery("Foo").Eq("turanga", "10")
   204  			q1 = NewQuery("Foo").Eq("turanga", "10")
   205  
   206  			So(q.Less(q1), ShouldBeFalse)
   207  			So(q1.Less(q), ShouldBeFalse)
   208  
   209  			// "... turanga == 10 ..." compare "... zoidberg == 10 ..."
   210  			q = NewQuery("Foo").Eq("turanga", "10")
   211  			q1 = NewQuery("Foo").Eq("zoidberg", "10")
   212  
   213  			So(q.Less(q1), ShouldBeFalse)
   214  			So(q1.Less(q), ShouldBeTrue)
   215  
   216  			// "... turanga == 10 && turanga == 20 ..." compare "... turanga == 10 ..."
   217  			q = NewQuery("Foo").Eq("turanga", "10", "20")
   218  			q1 = NewQuery("Foo").Eq("turanga", "10")
   219  
   220  			So(q.Less(q1), ShouldBeFalse)
   221  			So(q1.Less(q), ShouldBeTrue)
   222  
   223  			// "... turanga == 10 ..." compare "..."
   224  			q = NewQuery("Foo").Eq("turanga", "10")
   225  			q1 = NewQuery("Foo")
   226  
   227  			So(q.Less(q1), ShouldBeFalse)
   228  			So(q1.Less(q), ShouldBeTrue)
   229  		})
   230  		Convey("Compare In filter", func() {
   231  			// Equal. Order of filter and values doesn't matter.
   232  			q1 := NewQuery("Foo").In("p2", "x", "y").In("p1", "a", "b")
   233  			q2 := NewQuery("Foo").In("p1", "a", "b").In("p2", "y", "x")
   234  			So(q1.Less(q2), ShouldBeFalse)
   235  			So(q2.Less(q1), ShouldBeFalse)
   236  
   237  			q1 = NewQuery("Foo").In("prop", "a")
   238  			q2 = NewQuery("Foo").In("prop", "b")
   239  			So(q1.Less(q2), ShouldBeTrue)
   240  			So(q2.Less(q1), ShouldBeFalse)
   241  
   242  			q1 = NewQuery("Foo").In("prop", "a")
   243  			q2 = NewQuery("Foo").In("prop", "a", "b")
   244  			So(q1.Less(q2), ShouldBeTrue)
   245  			So(q2.Less(q1), ShouldBeFalse)
   246  
   247  			q1 = NewQuery("Foo").In("prop", "a")
   248  			q2 = NewQuery("Foo").In("prop", "a").In("prop", "b")
   249  			So(q1.Less(q2), ShouldBeTrue)
   250  			So(q2.Less(q1), ShouldBeFalse)
   251  
   252  			q1 = NewQuery("Foo").In("prop", "a")
   253  			q2 = NewQuery("Foo").In("prop", "a").In("another", "a")
   254  			So(q1.Less(q2), ShouldBeTrue)
   255  			So(q2.Less(q1), ShouldBeFalse)
   256  		})
   257  		Convey("Compare inequality", func() {
   258  			// "... turanga < 10 ..." compare "... turanga < 24 ..."
   259  			q := NewQuery("Foo").Lt("turanga", "10")
   260  			q1 := NewQuery("Foo").Lt("turanga", "24")
   261  
   262  			So(q.Less(q1), ShouldBeTrue)
   263  			So(q1.Less(q), ShouldBeFalse)
   264  
   265  			// "... turanga < 10 ..." compare "... turanga < 10 ..."
   266  			q = NewQuery("Foo").Lt("turanga", "10")
   267  			q1 = NewQuery("Foo").Lt("turanga", "10")
   268  
   269  			So(q.Less(q1), ShouldBeFalse)
   270  			So(q1.Less(q), ShouldBeFalse)
   271  
   272  			// "... turanga < 10 ..." compare "... zoidberg < 10 ..."
   273  			q = NewQuery("Foo").Lt("turanga", "10")
   274  			q1 = NewQuery("Foo").Lt("zoidberg", "10")
   275  
   276  			So(q.Less(q1), ShouldBeTrue)
   277  			So(q1.Less(q), ShouldBeFalse)
   278  
   279  			// "... turanga <= 10 ..." compare "... turanga <= 24 ..."
   280  			q = NewQuery("Foo").Lte("turanga", "10")
   281  			q1 = NewQuery("Foo").Lte("turanga", "24")
   282  
   283  			So(q.Less(q1), ShouldBeTrue)
   284  			So(q1.Less(q), ShouldBeFalse)
   285  
   286  			// "... turanga <= 10 ..." compare "... turanga <= 10 ..."
   287  			q = NewQuery("Foo").Lte("turanga", "10")
   288  			q1 = NewQuery("Foo").Lte("turanga", "10")
   289  
   290  			So(q.Less(q1), ShouldBeFalse)
   291  			So(q1.Less(q), ShouldBeFalse)
   292  
   293  			// "... turanga <= 10 ..." compare "... zoidberg <= 10 ..."
   294  			q = NewQuery("Foo").Lte("turanga", "10")
   295  			q1 = NewQuery("Foo").Lte("zoidberg", "10")
   296  
   297  			So(q.Less(q1), ShouldBeTrue)
   298  			So(q1.Less(q), ShouldBeFalse)
   299  
   300  			// "... turanga < 10 ..." compare "... turanga <= 10 ..."
   301  			q = NewQuery("Foo").Lt("turanga", "10")
   302  			q1 = NewQuery("Foo").Lte("turanga", "10")
   303  
   304  			So(q.Less(q1), ShouldBeTrue)
   305  			So(q1.Less(q), ShouldBeFalse)
   306  
   307  			// "... turanga < 10 ..." compare "... turanga > 10 ..."
   308  			q = NewQuery("Foo").Lt("turanga", "10")
   309  			q1 = NewQuery("Foo").Gt("turanga", "10")
   310  
   311  			So(q.Less(q1), ShouldBeTrue)
   312  			So(q1.Less(q), ShouldBeFalse)
   313  
   314  			// "... turanga <= 10 ..." compare "... turanga >= 10 ..."
   315  			q = NewQuery("Foo").Lte("turanga", "10")
   316  			q1 = NewQuery("Foo").Gte("turanga", "10")
   317  
   318  			So(q.Less(q1), ShouldBeTrue)
   319  			So(q1.Less(q), ShouldBeFalse)
   320  
   321  			// "... turanga > 10 ..." compare "... turanga > 24 ..."
   322  			q = NewQuery("Foo").Gt("turanga", "10")
   323  			q1 = NewQuery("Foo").Gt("turanga", "24")
   324  
   325  			So(q.Less(q1), ShouldBeTrue)
   326  			So(q1.Less(q), ShouldBeFalse)
   327  
   328  			// "... turanga > 10 ..." compare "... turanga > 10 ..."
   329  			q = NewQuery("Foo").Gt("turanga", "10")
   330  			q1 = NewQuery("Foo").Gt("turanga", "10")
   331  
   332  			So(q.Less(q1), ShouldBeFalse)
   333  			So(q1.Less(q), ShouldBeFalse)
   334  
   335  			// "... turanga > 10 ..." compare "... zoidberg > 10 ..."
   336  			q = NewQuery("Foo").Gt("turanga", "10")
   337  			q1 = NewQuery("Foo").Gt("zoidberg", "10")
   338  
   339  			So(q.Less(q1), ShouldBeTrue)
   340  			So(q1.Less(q), ShouldBeFalse)
   341  
   342  			// "... turanga >= 10 ..." compare "... zoidberg >= 24 ..."
   343  			q = NewQuery("Foo").Gte("turanga", "10")
   344  			q1 = NewQuery("Foo").Gte("turanga", "24")
   345  
   346  			So(q.Less(q1), ShouldBeTrue)
   347  			So(q1.Less(q), ShouldBeFalse)
   348  
   349  			// "... turanga >= 10 ..." compare "... turanga >= 10 ..."
   350  			q = NewQuery("Foo").Gte("turanga", "10")
   351  			q1 = NewQuery("Foo").Gte("turanga", "10")
   352  
   353  			So(q.Less(q1), ShouldBeFalse)
   354  			So(q1.Less(q), ShouldBeFalse)
   355  
   356  			// "... turanga >= 10 ..." compare "... zoidberg >= 10 ..."
   357  			q = NewQuery("Foo").Gte("turanga", "10")
   358  			q1 = NewQuery("Foo").Gte("zoidberg", "10")
   359  
   360  			So(q.Less(q1), ShouldBeTrue)
   361  			So(q1.Less(q), ShouldBeFalse)
   362  
   363  			// "... turanga > 10 ..." compare "... turanga >= 10 ..."
   364  			q = NewQuery("Foo").Gt("turanga", "10")
   365  			q1 = NewQuery("Foo").Gte("turanga", "10")
   366  
   367  			So(q.Less(q1), ShouldBeTrue)
   368  			So(q1.Less(q), ShouldBeFalse)
   369  		})
   370  
   371  		Convey("Composite comparison", func() {
   372  			a := NewQuery("Foo").Project("conrad").Distinct(false)
   373  			b := NewQuery("Foo").Project("conrad").Distinct(true)
   374  			So(a.Less(b), ShouldBeTrue)
   375  			So(b.Less(a), ShouldBeFalse)
   376  
   377  			a = NewQuery("Foo").Eq("Prop", "val").Offset(100)
   378  			b = NewQuery("Foo").Eq("Prop", "val").Offset(200)
   379  			So(a.Less(b), ShouldBeTrue)
   380  			So(b.Less(a), ShouldBeFalse)
   381  		})
   382  	})
   383  }
   384  
   385  type queryTest struct {
   386  	// name is the name of the test case
   387  	name string
   388  
   389  	// q is the input query
   390  	q *Query
   391  
   392  	// gql is the expected generated GQL.
   393  	gql string
   394  
   395  	// assertion is the error to expect after prepping the query, or nil if the
   396  	// error should be nil.
   397  	assertion func(err error)
   398  
   399  	// equivalentQuery is another query which ShouldResemble q. This is useful to
   400  	// see the effects of redundancy pruning on e.g. filters.
   401  	equivalentQuery *Query
   402  }
   403  
   404  func nq(kinds ...string) *Query {
   405  	kind := "Foo"
   406  	if len(kinds) > 0 {
   407  		kind = kinds[0]
   408  	}
   409  	return NewQuery(kind)
   410  }
   411  
   412  func mkKey(elems ...any) *Key {
   413  	return MkKeyContext("s~aid", "ns").MakeKey(elems...)
   414  }
   415  
   416  func errString(v string) func(error) {
   417  	return func(err error) {
   418  		So(err, ShouldErrLike, v)
   419  	}
   420  }
   421  
   422  func shouldBeErrInvalidKey(err error) {
   423  	So(IsErrInvalidKey(err), ShouldBeTrue)
   424  }
   425  
   426  var queryTests = []queryTest{
   427  	{"only one inequality",
   428  		nq().Order("bob", "wat").Gt("bob", 10).Lt("wat", 29),
   429  		"",
   430  		errString("inequality filters on multiple properties"),
   431  		nil},
   432  
   433  	{"bad order",
   434  		nq().Order("+Bob"),
   435  		"",
   436  		errString("invalid order"), nil},
   437  
   438  	{"empty order",
   439  		nq().Order(""),
   440  		"",
   441  		errString("empty order"), nil},
   442  
   443  	{"negative offset disables Offset",
   444  		nq().Offset(100).Offset(-20),
   445  		"SELECT * FROM `Foo` ORDER BY `__key__`",
   446  		nil, nq()},
   447  
   448  	{"projecting a keys-only query",
   449  		nq().Project("hello").KeysOnly(true),
   450  		"",
   451  		errString("cannot project a keysOnly query"), nil},
   452  
   453  	{"projecting a keys-only query (reverse)",
   454  		nq().KeysOnly(true).Project("hello"),
   455  		"",
   456  		errString("cannot project a keysOnly query"), nil},
   457  
   458  	{"projecting an empty field",
   459  		nq().Project("hello", ""),
   460  		"",
   461  		errString("cannot filter/project on: \"\""), nil},
   462  
   463  	{"projecting __key__",
   464  		nq().Project("hello", "__key__"),
   465  		"",
   466  		errString("cannot project on \"__key__\""), nil},
   467  
   468  	{"getting all the keys",
   469  		nq("").KeysOnly(true),
   470  		"SELECT __key__ ORDER BY `__key__`",
   471  		nil, nil},
   472  
   473  	{"projecting a duplicate",
   474  		nq().Project("hello", "hello"),
   475  		"SELECT `hello` FROM `Foo` ORDER BY `hello`, `__key__`",
   476  		nil, nq().Project("hello")},
   477  
   478  	{"projecting a duplicate (style 2)",
   479  		nq().Project("hello").Project("hello"),
   480  		"SELECT `hello` FROM `Foo` ORDER BY `hello`, `__key__`",
   481  		nil, nq().Project("hello")},
   482  
   483  	{"project distinct",
   484  		nq().Project("hello").Distinct(true),
   485  		"SELECT DISTINCT `hello` FROM `Foo` ORDER BY `hello`, `__key__`",
   486  		nil, nil},
   487  
   488  	{"projecting in an inequality query",
   489  		nq().Gte("foo", 10).Project("bar").Distinct(true),
   490  		"SELECT DISTINCT `bar`, `foo` FROM `Foo` WHERE `foo` >= 10 ORDER BY `foo`, `bar`, `__key__`",
   491  		nil, nil},
   492  
   493  	{"bad ancestors",
   494  		nq().Ancestor(mkKey("goop", 0)),
   495  		"",
   496  		shouldBeErrInvalidKey, nil},
   497  
   498  	{"nil ancestors",
   499  		nq().Ancestor(nil),
   500  		"SELECT * FROM `Foo` ORDER BY `__key__`",
   501  		nil, nq()},
   502  
   503  	{"Bad key filters",
   504  		nq().Gt("__key__", mkKey("goop", 0)),
   505  		"",
   506  		shouldBeErrInvalidKey, nil},
   507  
   508  	{"filters for __key__ that aren't keys",
   509  		nq().Gt("__key__", 10),
   510  		"",
   511  		errString("filters on \"__key__\" must have type *Key"), nil},
   512  
   513  	{"multiple inequalities",
   514  		nq().Gt("bob", 19).Lt("charlie", 20),
   515  		"",
   516  		errString("inequality filters on multiple properties"), nil},
   517  
   518  	{"inequality must be first sort order",
   519  		nq().Gt("bob", 19).Order("-charlie"),
   520  		"",
   521  		errString("first sort order"), nil},
   522  
   523  	{"inequality must be first sort order (reverse)",
   524  		nq().Order("-charlie").Gt("bob", 19),
   525  		"",
   526  		errString("first sort order"), nil},
   527  
   528  	{"equality filter projected field",
   529  		nq().Project("foo").Eq("foo", 10),
   530  		"",
   531  		errString("cannot project"), nil},
   532  
   533  	{"equality filter projected field (reverse)",
   534  		nq().Eq("foo", 10).Project("foo"),
   535  		"",
   536  		errString("cannot project"), nil},
   537  
   538  	{"equality filter projected field (in)",
   539  		nq().Project("foo").In("foo", 10),
   540  		"",
   541  		errString("cannot project"), nil},
   542  
   543  	{"kindless with non-__key__ filters",
   544  		nq("").Lt("face", 25.3),
   545  		"",
   546  		errString("kindless queries can only filter on __key__"), nil},
   547  
   548  	{"kindless with non-__key__ orders",
   549  		nq("").Order("face"),
   550  		"",
   551  		errString("invalid order for kindless query"), nil},
   552  
   553  	{"kindless with descending-__key__ order",
   554  		nq("").Order("-__key__"),
   555  		"",
   556  		errString("invalid order for kindless query"), nil},
   557  
   558  	{"kindless with equality filters",
   559  		nq("").Eq("hello", 1),
   560  		"",
   561  		errString("may not have any equality"), nil},
   562  
   563  	{"kindless with equality filters (in)",
   564  		nq("").In("hello", 1),
   565  		"",
   566  		errString("may not have any equality"), nil},
   567  
   568  	{"kindless with ancestor filter",
   569  		nq("").Ancestor(mkKey("Parent", 1)),
   570  		"SELECT * WHERE __key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Parent\", 1) ORDER BY `__key__`",
   571  		nil, nil},
   572  
   573  	{"kindless with ancestor filter and __key__ ineq",
   574  		nq("").Ancestor(mkKey("Parent", 1)).Lt("__key__", mkKey("Parent", 1, "Sub", "hat")),
   575  		"SELECT * WHERE `__key__` < KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Parent\", 1, \"Sub\", \"hat\") AND __key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Parent\", 1) ORDER BY `__key__`",
   576  		nil, nil},
   577  
   578  	{"distinct non-projection",
   579  		nq().Distinct(true).Gt("marla", 1),
   580  		"SELECT * FROM `Foo` WHERE `marla` > 1 ORDER BY `marla`, `__key__`",
   581  		nil, nq().Gt("marla", 1)},
   582  
   583  	{"chained errors return the first",
   584  		nq().Eq("__reserved__", 100).Eq("hello", "wurld").Order(""),
   585  		"",
   586  		errString("__reserved__"), nil},
   587  
   588  	{"multiple ancestors",
   589  		nq().Ancestor(mkKey("something", "correct")).Ancestor(mkKey("something", "else")),
   590  		("SELECT * FROM `Foo` " +
   591  			"WHERE __key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"something\", \"else\") " +
   592  			"ORDER BY `__key__`"),
   593  		nil, nq().Ancestor(mkKey("something", "else"))},
   594  
   595  	{"filter with illegal type",
   596  		nq().Eq("something", complex(1, 2)),
   597  		"",
   598  		errString("bad type complex"), nil},
   599  
   600  	{"filter with non-comparable type",
   601  		nq().Eq("something", PropertyMap{}),
   602  		"",
   603  		errString("a non-comparable value"), nil},
   604  
   605  	{"sort orders used for equality are ignored",
   606  		nq().Order("a", "b", "c").Eq("b", 2, 2),
   607  		"SELECT * FROM `Foo` WHERE `b` = 2 ORDER BY `a`, `c`, `__key__`",
   608  		nil, nq().Order("a", "c").Eq("b", 2)},
   609  
   610  	{"sort orders used for equality are ignored (reversed)",
   611  		nq().Eq("b", 2).Order("a", "b", "c"),
   612  		"SELECT * FROM `Foo` WHERE `b` = 2 ORDER BY `a`, `c`, `__key__`",
   613  		nil,
   614  		nq().Order("a", "c").Eq("b", 2)},
   615  
   616  	{"duplicate equality filters are ignored",
   617  		nq().Eq("b", 10, -1, 2, 2, 7, 1, 2, 10, -1, 7, 1, 2),
   618  		"SELECT * FROM `Foo` WHERE `b` = -1 AND `b` = 1 AND `b` = 2 AND `b` = 7 AND `b` = 10 ORDER BY `__key__`",
   619  		nil,
   620  		nq().Eq("b", -1, 1, 2, 7, 10)},
   621  
   622  	{"duplicate orders are ignored",
   623  		nq().Order("a").Order("a").Order("a"),
   624  		"SELECT * FROM `Foo` ORDER BY `a`, `__key__`",
   625  		nil,
   626  		nq().Order("a")},
   627  
   628  	{"Filtering on a reserved property is forbidden",
   629  		nq().Gte("__special__", 10),
   630  		"",
   631  		errString("cannot filter/project on reserved property: \"__special__\""),
   632  		nil},
   633  
   634  	{"in-bound key filters with ancestor OK",
   635  		nq().Ancestor(mkKey("Hello", 10)).Lte("__key__", mkKey("Hello", 10, "Something", "hi")),
   636  		("SELECT * FROM `Foo` " +
   637  			"WHERE `__key__` <= KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Hello\", 10, \"Something\", \"hi\") AND " +
   638  			"__key__ HAS ANCESTOR KEY(DATASET(\"s~aid\"), NAMESPACE(\"ns\"), \"Hello\", 10) " +
   639  			"ORDER BY `__key__`"),
   640  		nil,
   641  		nil},
   642  
   643  	{"projection elements get filled in",
   644  		nq().Project("Foo", "Bar").Order("-Bar"),
   645  		"SELECT `Bar`, `Foo` FROM `Foo` ORDER BY `Bar` DESC, `Foo`, `__key__`",
   646  		nil, nq().Project("Foo", "Bar").Order("-Bar").Order("Foo")},
   647  
   648  	{"query without anything is fine",
   649  		nq(),
   650  		"SELECT * FROM `Foo` ORDER BY `__key__`",
   651  		nil,
   652  		nil},
   653  
   654  	{"ineq on __key__ with ancestor must be an ancestor of __ancestor__!",
   655  		nq().Ancestor(mkKey("Hello", 10)).Lt("__key__", mkKey("Hello", 8)),
   656  		"",
   657  		errString("inequality filters on __key__ must be descendants of the __ancestor__"),
   658  		nil},
   659  
   660  	{"ineq on __key__ with ancestor must be an ancestor of __ancestor__! (2)",
   661  		nq().Ancestor(mkKey("Hello", 10)).Gt("__key__", mkKey("Hello", 8)),
   662  		"",
   663  		errString("inequality filters on __key__ must be descendants of the __ancestor__"),
   664  		nil},
   665  
   666  	{"can build an empty query",
   667  		nq().Lt("hello", 10).Gt("hello", 50),
   668  		"",
   669  		func(err error) { So(err, ShouldEqual, ErrNullQuery) },
   670  		nil},
   671  
   672  	{"can build an empty query (in)",
   673  		nq().In("prop"),
   674  		"",
   675  		func(err error) { So(err, ShouldEqual, ErrNullQuery) },
   676  		nil},
   677  
   678  	{"reject pseudofield $id",
   679  		nq().Lt("$id", 10),
   680  		"",
   681  		func(err error) { So(err, ShouldErrLike, "rejecting field") },
   682  		nil},
   683  
   684  	{"IN filters",
   685  		nq().In("str", "a", "b").In("str", "c").In("int", 1, 2),
   686  		"SELECT * FROM `Foo` WHERE `int` IN ARRAY(1, 2) AND `str` IN ARRAY(\"a\", \"b\") AND `str` IN ARRAY(\"c\") ORDER BY `__key__`",
   687  		nil, nil},
   688  }
   689  
   690  func TestQueries(t *testing.T) {
   691  	t.Parallel()
   692  
   693  	Convey("queries have tons of condition checking", t, func() {
   694  		for _, tc := range queryTests {
   695  			Convey(tc.name, func() {
   696  				fq, err := tc.q.Finalize()
   697  				if err == nil {
   698  					err = fq.Valid(MkKeyContext("s~aid", "ns"))
   699  				}
   700  				if tc.assertion != nil {
   701  					tc.assertion(err)
   702  				} else {
   703  					So(err, ShouldBeNil)
   704  				}
   705  
   706  				if tc.gql != "" {
   707  					So(fq.GQL(), ShouldEqual, tc.gql)
   708  				}
   709  
   710  				if tc.equivalentQuery != nil {
   711  					fq2, err := tc.equivalentQuery.Finalize()
   712  					So(err, ShouldBeNil)
   713  
   714  					fq.original = nil
   715  					fq2.original = nil
   716  					So(fq, ShouldResemble, fq2)
   717  				}
   718  			})
   719  		}
   720  	})
   721  }
   722  
   723  func TestQueryConcurrencySafety(t *testing.T) {
   724  	t.Parallel()
   725  
   726  	Convey("query and derivative query finalization is goroutine-safe", t, func() {
   727  		const rounds = 10
   728  
   729  		q := NewQuery("Foo")
   730  
   731  		err := parallel.FanOutIn(func(outerC chan<- func() error) {
   732  
   733  			for i := 0; i < rounds; i++ {
   734  				outerQ := q.Gt("Field", i)
   735  
   736  				// Finalize the original query.
   737  				outerC <- func() error {
   738  					_, err := q.Finalize()
   739  					return err
   740  				}
   741  
   742  				// Finalize the derivative query a lot.
   743  				outerC <- func() error {
   744  					return parallel.FanOutIn(func(innerC chan<- func() error) {
   745  						for i := 0; i < rounds; i++ {
   746  							innerC <- func() error {
   747  								_, err := outerQ.Finalize()
   748  								return err
   749  							}
   750  						}
   751  					})
   752  				}
   753  			}
   754  		})
   755  		So(err, ShouldBeNil)
   756  	})
   757  }