go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/filter/txnBuf/txnbuf_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 txnBuf
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"math/rand"
    22  	"testing"
    23  
    24  	"go.chromium.org/luci/common/data/cmpbin"
    25  	"go.chromium.org/luci/common/errors"
    26  
    27  	"go.chromium.org/luci/gae/filter/count"
    28  	"go.chromium.org/luci/gae/impl/memory"
    29  	ds "go.chromium.org/luci/gae/service/datastore"
    30  	"go.chromium.org/luci/gae/service/info"
    31  
    32  	. "github.com/smartystreets/goconvey/convey"
    33  	. "go.chromium.org/luci/common/testing/assertions"
    34  )
    35  
    36  type Foo struct {
    37  	ID     int64   `gae:"$id"`
    38  	Parent *ds.Key `gae:"$parent"`
    39  
    40  	Value   []int64
    41  	ValueNI []byte `gae:",noindex"`
    42  	Sort    []string
    43  }
    44  
    45  func toIntSlice(stuff []any) []int64 {
    46  	vals, ok := stuff[0].([]int64)
    47  	if !ok {
    48  		vals = make([]int64, len(stuff))
    49  		for i := range vals {
    50  			vals[i] = int64(stuff[i].(int))
    51  		}
    52  	}
    53  	return vals
    54  }
    55  
    56  func toInt64(thing any) int64 {
    57  	switch x := thing.(type) {
    58  	case int:
    59  		return int64(x)
    60  	case int64:
    61  		return x
    62  	default:
    63  		panic(fmt.Errorf("wat r it? %v", x))
    64  	}
    65  }
    66  
    67  func fooShouldHave(c context.Context) func(any, ...any) string {
    68  	return func(id any, values ...any) string {
    69  		f := &Foo{ID: toInt64(id)}
    70  		err := ds.Get(c, f)
    71  		if len(values) == 0 {
    72  			return ShouldEqual(err, ds.ErrNoSuchEntity)
    73  		}
    74  
    75  		ret := ShouldBeNil(err)
    76  		if ret == "" {
    77  			if data, ok := values[0].([]byte); ok {
    78  				ret = ShouldResemble(f.ValueNI, data)
    79  			} else {
    80  				ret = ShouldResemble(f.Value, toIntSlice(values))
    81  			}
    82  		}
    83  		return ret
    84  	}
    85  }
    86  
    87  func fooSetTo(c context.Context) func(any, ...any) string {
    88  	return func(id any, values ...any) string {
    89  		f := &Foo{ID: toInt64(id)}
    90  		if len(values) == 0 {
    91  			return ShouldBeNil(ds.Delete(c, ds.KeyForObj(c, f)))
    92  		}
    93  		if data, ok := values[0].([]byte); ok {
    94  			f.ValueNI = data
    95  		} else {
    96  			f.Value = toIntSlice(values)
    97  		}
    98  		return ShouldBeNil(ds.Put(c, f))
    99  	}
   100  }
   101  
   102  const dataLen = 26
   103  
   104  var (
   105  	dataMultiRoot  = make([]*Foo, dataLen)
   106  	dataSingleRoot = make([]*Foo, dataLen)
   107  	hugeField      = make([]byte, DefaultSizeBudget/8)
   108  	hugeData       = make([]*Foo, 11)
   109  	root           = ds.MkKeyContext("something~else", "").MakeKey("Parent", 1)
   110  )
   111  
   112  func init() {
   113  	cb := func(i int64) string {
   114  		buf := &bytes.Buffer{}
   115  		cmpbin.WriteInt(buf, i)
   116  		return buf.String()
   117  	}
   118  
   119  	rs := rand.NewSource(0)
   120  	nums := make([]string, dataLen)
   121  	for i := range dataMultiRoot {
   122  		id := int64(i + 1)
   123  		nums[i] = cb(id)
   124  
   125  		val := make([]int64, (rs.Int63()%dataLen)+1)
   126  		for j := range val {
   127  			r := rs.Int63()
   128  			val[j] = r
   129  		}
   130  
   131  		dataMultiRoot[i] = &Foo{ID: id, Value: val}
   132  		dataSingleRoot[i] = &Foo{ID: id, Parent: root, Value: val}
   133  	}
   134  
   135  	for i := range hugeField {
   136  		hugeField[i] = byte(i)
   137  	}
   138  
   139  	for i := range hugeData {
   140  		hugeData[i] = &Foo{ID: int64(i + 1), ValueNI: hugeField}
   141  	}
   142  }
   143  
   144  func mkds(data []*Foo) (under, over *count.DSCounter, c context.Context) {
   145  	c = memory.UseWithAppID(context.Background(), "something~else")
   146  
   147  	dataKey := ds.KeyForObj(c, data[0])
   148  	if err := ds.AllocateIDs(c, ds.NewIncompleteKeys(c, 100, dataKey.Kind(), dataKey.Parent())); err != nil {
   149  		panic(err)
   150  	}
   151  	if err := ds.Put(c, data); err != nil {
   152  		panic(err)
   153  	}
   154  
   155  	c, under = count.FilterRDS(c)
   156  	c = FilterRDS(c)
   157  	c, over = count.FilterRDS(c)
   158  	return
   159  }
   160  
   161  func TestTransactionBuffers(t *testing.T) {
   162  	t.Parallel()
   163  
   164  	Convey("Get/Put/Delete", t, func() {
   165  		under, over, c := mkds(dataMultiRoot)
   166  		ds.GetTestable(c).SetTransactionRetryCount(1)
   167  
   168  		So(under.PutMulti.Total(), ShouldEqual, 0)
   169  		So(over.PutMulti.Total(), ShouldEqual, 0)
   170  
   171  		Convey("Good", func() {
   172  			Convey("read-only", func() {
   173  				So(ds.RunInTransaction(c, func(c context.Context) error {
   174  					So(4, fooShouldHave(c), dataMultiRoot[3].Value)
   175  					return nil
   176  				}, nil), ShouldBeNil)
   177  			})
   178  
   179  			Convey("single-level read/write", func() {
   180  				So(ds.RunInTransaction(c, func(c context.Context) error {
   181  					So(4, fooShouldHave(c), dataMultiRoot[3].Value)
   182  
   183  					So(4, fooSetTo(c), 1, 2, 3, 4)
   184  
   185  					So(3, fooSetTo(c), 1, 2, 3, 4)
   186  
   187  					// look! it remembers :)
   188  					So(4, fooShouldHave(c), 1, 2, 3, 4)
   189  					return nil
   190  				}, nil), ShouldBeNil)
   191  
   192  				// 2 because we are simulating a transaction failure
   193  				So(under.PutMulti.Total(), ShouldEqual, 2)
   194  
   195  				So(3, fooShouldHave(c), 1, 2, 3, 4)
   196  				So(4, fooShouldHave(c), 1, 2, 3, 4)
   197  			})
   198  
   199  			Convey("multi-level read/write", func() {
   200  				So(ds.RunInTransaction(c, func(c context.Context) error {
   201  					So(3, fooShouldHave(c), dataMultiRoot[2].Value)
   202  
   203  					So(3, fooSetTo(c), 1, 2, 3, 4)
   204  					So(7, fooSetTo(c))
   205  
   206  					vals := []*Foo{
   207  						{ID: 793},
   208  						{ID: 7},
   209  						{ID: 3},
   210  						{ID: 4},
   211  					}
   212  					So(ds.Get(c, vals), ShouldResemble, errors.NewMultiError(
   213  						ds.ErrNoSuchEntity,
   214  						ds.ErrNoSuchEntity,
   215  						nil,
   216  						nil,
   217  					))
   218  
   219  					So(vals[0].Value, ShouldBeNil)
   220  					So(vals[1].Value, ShouldBeNil)
   221  					So(vals[2].Value, ShouldResemble, []int64{1, 2, 3, 4})
   222  					So(vals[3].Value, ShouldResemble, dataSingleRoot[3].Value)
   223  
   224  					// inner, failing, transaction
   225  					So(ds.RunInTransaction(c, func(c context.Context) error {
   226  						// we can see stuff written in the outer txn
   227  						So(7, fooShouldHave(c))
   228  						So(3, fooShouldHave(c), 1, 2, 3, 4)
   229  
   230  						So(3, fooSetTo(c), 10, 20, 30, 40)
   231  
   232  						// disaster strikes!
   233  						return errors.New("whaaaa")
   234  					}, nil), ShouldErrLike, "whaaaa")
   235  
   236  					So(3, fooShouldHave(c), 1, 2, 3, 4)
   237  
   238  					// inner, successful, transaction
   239  					So(ds.RunInTransaction(c, func(c context.Context) error {
   240  						So(3, fooShouldHave(c), 1, 2, 3, 4)
   241  						So(3, fooSetTo(c), 10, 20, 30, 40)
   242  						return nil
   243  					}, nil), ShouldBeNil)
   244  
   245  					// now we see it
   246  					So(3, fooShouldHave(c), 10, 20, 30, 40)
   247  					return nil
   248  				}, nil), ShouldBeNil)
   249  
   250  				// 2 because we are simulating a transaction failure
   251  				So(under.PutMulti.Total(), ShouldEqual, 2)
   252  				So(under.DeleteMulti.Total(), ShouldEqual, 2)
   253  
   254  				So(over.PutMulti.Total(), ShouldEqual, 8)
   255  
   256  				So(7, fooShouldHave(c))
   257  				So(3, fooShouldHave(c), 10, 20, 30, 40)
   258  			})
   259  
   260  			Convey("can allocate IDs from an inner transaction", func() {
   261  				nums := []int64{4, 8, 15, 16, 23, 42}
   262  				k := (*ds.Key)(nil)
   263  				So(ds.RunInTransaction(c, func(c context.Context) error {
   264  					So(ds.RunInTransaction(c, func(c context.Context) error {
   265  						f := &Foo{Value: nums}
   266  						So(ds.Put(c, f), ShouldBeNil)
   267  						k = ds.KeyForObj(c, f)
   268  						return nil
   269  					}, nil), ShouldBeNil)
   270  
   271  					So(k.IntID(), fooShouldHave(c), nums)
   272  
   273  					return nil
   274  				}, nil), ShouldBeNil)
   275  
   276  				So(k.IntID(), fooShouldHave(c), nums)
   277  			})
   278  
   279  		})
   280  
   281  		Convey("Bad", func() {
   282  
   283  			Convey("too many roots", func() {
   284  				So(ds.RunInTransaction(c, func(c context.Context) error {
   285  					for i := 1; i < 26; i++ {
   286  						f := &Foo{ID: int64(i)}
   287  						So(ds.Get(c, f), ShouldBeNil)
   288  						So(f, ShouldResemble, dataMultiRoot[i-1])
   289  					}
   290  
   291  					f := &Foo{ID: 7}
   292  					f.Value = []int64{9}
   293  					So(ds.Put(c, f), ShouldBeNil)
   294  
   295  					return nil
   296  				}, nil), ShouldBeNil)
   297  
   298  				f := &Foo{ID: 7}
   299  				So(ds.Get(c, f), ShouldBeNil)
   300  				So(f.Value, ShouldResemble, []int64{9})
   301  			})
   302  
   303  			Convey("buffered errors never reach the datastore", func() {
   304  				So(ds.RunInTransaction(c, func(c context.Context) error {
   305  					So(ds.Put(c, &Foo{ID: 1, Value: []int64{1, 2, 3, 4}}), ShouldBeNil)
   306  					return errors.New("boop")
   307  				}, nil), ShouldErrLike, "boop")
   308  				So(under.PutMulti.Total(), ShouldEqual, 0)
   309  				So(over.PutMulti.Successes(), ShouldEqual, 1)
   310  			})
   311  
   312  		})
   313  
   314  	})
   315  }
   316  
   317  func TestHuge(t *testing.T) {
   318  	t.Parallel()
   319  
   320  	Convey("testing datastore enforces thresholds", t, func() {
   321  		_, _, c := mkds(dataMultiRoot)
   322  
   323  		Convey("exceeding inner txn size threshold still allows outer", func() {
   324  			So(ds.RunInTransaction(c, func(c context.Context) error {
   325  				So(18, fooSetTo(c), hugeField)
   326  
   327  				So(ds.RunInTransaction(c, func(c context.Context) error {
   328  					So(ds.Put(c, hugeData), ShouldBeNil)
   329  					return nil
   330  				}, nil), ShouldErrLike, ErrTransactionTooLarge)
   331  
   332  				return nil
   333  			}, nil), ShouldBeNil)
   334  
   335  			So(18, fooShouldHave(c), hugeField)
   336  		})
   337  
   338  		Convey("exceeding inner txn count threshold still allows outer", func() {
   339  			So(ds.RunInTransaction(c, func(c context.Context) error {
   340  				So(18, fooSetTo(c), hugeField)
   341  
   342  				So(ds.RunInTransaction(c, func(c context.Context) error {
   343  					p := ds.MakeKey(c, "mom", 1)
   344  
   345  					// This will exceed the budget, since we've already done one write in
   346  					// the parent.
   347  					for i := 1; i <= DefaultWriteCountBudget; i++ {
   348  						So(ds.Put(c, &Foo{ID: int64(i), Parent: p}), ShouldBeNil)
   349  					}
   350  					return nil
   351  				}, nil), ShouldErrLike, ErrTransactionTooLarge)
   352  
   353  				return nil
   354  			}, nil), ShouldBeNil)
   355  
   356  			So(18, fooShouldHave(c), hugeField)
   357  		})
   358  
   359  		Convey("exceeding threshold in the parent, then retreating in the child is okay", func() {
   360  			So(ds.RunInTransaction(c, func(c context.Context) error {
   361  				So(ds.Put(c, hugeData), ShouldBeNil)
   362  				So(18, fooSetTo(c), hugeField)
   363  
   364  				// We're over threshold! But the child will delete most of this and
   365  				// bring us back to normal.
   366  				So(ds.RunInTransaction(c, func(c context.Context) error {
   367  					keys := make([]*ds.Key, len(hugeData))
   368  					for i, d := range hugeData {
   369  						keys[i] = ds.KeyForObj(c, d)
   370  					}
   371  					return ds.Delete(c, keys)
   372  				}, nil), ShouldBeNil)
   373  
   374  				return nil
   375  			}, nil), ShouldBeNil)
   376  
   377  			So(18, fooShouldHave(c), hugeField)
   378  		})
   379  	})
   380  }
   381  
   382  func TestQuerySupport(t *testing.T) {
   383  	t.Parallel()
   384  
   385  	Convey("Queries", t, func() {
   386  		Convey("Good", func() {
   387  			q := ds.NewQuery("Foo").Ancestor(root)
   388  
   389  			Convey("normal", func() {
   390  				_, _, c := mkds(dataSingleRoot)
   391  				ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{
   392  					Kind:     "Foo",
   393  					Ancestor: true,
   394  					SortBy: []ds.IndexColumn{
   395  						{Property: "Value"},
   396  					},
   397  				})
   398  
   399  				So(ds.RunInTransaction(c, func(c context.Context) error {
   400  					q = q.Lt("Value", 400000000000000000)
   401  
   402  					vals := []*Foo{}
   403  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   404  					So(len(vals), ShouldEqual, 15)
   405  
   406  					count, err := ds.Count(c, q)
   407  					So(err, ShouldBeNil)
   408  					So(count, ShouldEqual, 15)
   409  
   410  					f := &Foo{ID: 1, Parent: root}
   411  					So(ds.Get(c, f), ShouldBeNil)
   412  					f.Value = append(f.Value, 100)
   413  					So(ds.Put(c, f), ShouldBeNil)
   414  
   415  					// Wowee, zowee, merged queries!
   416  					vals2 := []*Foo{}
   417  					So(ds.GetAll(c, q, &vals2), ShouldBeNil)
   418  					So(len(vals2), ShouldEqual, 16)
   419  					So(vals2[0], ShouldResemble, f)
   420  
   421  					vals2 = []*Foo{}
   422  					So(ds.GetAll(c, q.Limit(2).Offset(1), &vals2), ShouldBeNil)
   423  					So(len(vals2), ShouldEqual, 2)
   424  					So(vals2, ShouldResemble, vals[:2])
   425  
   426  					return nil
   427  				}, nil), ShouldBeNil)
   428  			})
   429  
   430  			Convey("keysOnly", func() {
   431  				_, _, c := mkds([]*Foo{
   432  					{ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}},
   433  					{ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}},
   434  					{ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1}},
   435  					{ID: 5, Parent: root, Value: []int64{1, 70, 101}},
   436  				})
   437  
   438  				So(ds.RunInTransaction(c, func(c context.Context) error {
   439  					q = q.Eq("Value", 1).KeysOnly(true)
   440  					vals := []*ds.Key{}
   441  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   442  					So(len(vals), ShouldEqual, 3)
   443  					So(vals[2], ShouldResemble, ds.MakeKey(c, "Parent", 1, "Foo", 5))
   444  
   445  					// can remove keys
   446  					So(ds.Delete(c, ds.MakeKey(c, "Parent", 1, "Foo", 2)), ShouldBeNil)
   447  					vals = []*ds.Key{}
   448  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   449  					So(len(vals), ShouldEqual, 2)
   450  
   451  					// and add new ones
   452  					So(ds.Put(c, &Foo{ID: 1, Parent: root, Value: []int64{1, 7, 100}}), ShouldBeNil)
   453  					So(ds.Put(c, &Foo{ID: 7, Parent: root, Value: []int64{20, 1}}), ShouldBeNil)
   454  					vals = []*ds.Key{}
   455  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   456  					So(len(vals), ShouldEqual, 4)
   457  
   458  					So(vals[0].IntID(), ShouldEqual, 1)
   459  					So(vals[1].IntID(), ShouldEqual, 4)
   460  					So(vals[2].IntID(), ShouldEqual, 5)
   461  					So(vals[3].IntID(), ShouldEqual, 7)
   462  
   463  					return nil
   464  				}, nil), ShouldBeNil)
   465  			})
   466  
   467  			Convey("project", func() {
   468  				_, _, c := mkds([]*Foo{
   469  					{ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}},
   470  					{ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}},
   471  					{ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1}},
   472  					{ID: 5, Parent: root, Value: []int64{1, 70, 101}},
   473  				})
   474  
   475  				ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{
   476  					Kind:     "Foo",
   477  					Ancestor: true,
   478  					SortBy: []ds.IndexColumn{
   479  						{Property: "Value"},
   480  					},
   481  				})
   482  
   483  				So(ds.RunInTransaction(c, func(c context.Context) error {
   484  					count, err := ds.Count(c, q.Project("Value"))
   485  					So(err, ShouldBeNil)
   486  					So(count, ShouldEqual, 24)
   487  
   488  					q = q.Project("Value").Offset(4).Limit(10)
   489  
   490  					vals := []ds.PropertyMap{}
   491  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   492  					So(len(vals), ShouldEqual, 10)
   493  
   494  					expect := []struct {
   495  						id  int64
   496  						val int64
   497  					}{
   498  						{2, 3},
   499  						{3, 3},
   500  						{4, 3},
   501  						{2, 4},
   502  						{3, 4},
   503  						{2, 5},
   504  						{3, 5},
   505  						{4, 5},
   506  						{2, 6},
   507  						{3, 6},
   508  					}
   509  
   510  					for i, pm := range vals {
   511  						So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble,
   512  							ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id))
   513  						So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val)
   514  					}
   515  
   516  					// should remove 4 entries, but there are plenty more to fill
   517  					So(ds.Delete(c, ds.MakeKey(c, "Parent", 1, "Foo", 2)), ShouldBeNil)
   518  
   519  					vals = []ds.PropertyMap{}
   520  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   521  					So(len(vals), ShouldEqual, 10)
   522  
   523  					expect = []struct {
   524  						id  int64
   525  						val int64
   526  					}{
   527  						// note (3, 3) and (4, 3) are correctly missing because deleting
   528  						// 2 removed two entries which are hidden by the Offset(4).
   529  						{3, 4},
   530  						{3, 5},
   531  						{4, 5},
   532  						{3, 6},
   533  						{3, 7},
   534  						{4, 7},
   535  						{3, 8},
   536  						{3, 9},
   537  						{4, 9},
   538  						{4, 11},
   539  					}
   540  
   541  					for i, pm := range vals {
   542  						So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble,
   543  							ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id))
   544  						So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val)
   545  					}
   546  
   547  					So(ds.Put(c, &Foo{ID: 1, Parent: root, Value: []int64{3, 9}}), ShouldBeNil)
   548  
   549  					vals = []ds.PropertyMap{}
   550  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   551  					So(len(vals), ShouldEqual, 10)
   552  
   553  					expect = []struct {
   554  						id  int64
   555  						val int64
   556  					}{
   557  						// 'invisible' {1, 3} entry bumps the {4, 3} into view.
   558  						{4, 3},
   559  						{3, 4},
   560  						{3, 5},
   561  						{4, 5},
   562  						{3, 6},
   563  						{3, 7},
   564  						{4, 7},
   565  						{3, 8},
   566  						{1, 9},
   567  						{3, 9},
   568  						{4, 9},
   569  					}
   570  
   571  					for i, pm := range vals {
   572  						So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble,
   573  							ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id))
   574  						So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val)
   575  					}
   576  
   577  					return nil
   578  				}, nil), ShouldBeNil)
   579  
   580  			})
   581  
   582  			Convey("project+distinct", func() {
   583  				_, _, c := mkds([]*Foo{
   584  					{ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}},
   585  					{ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}},
   586  					{ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1}},
   587  					{ID: 5, Parent: root, Value: []int64{1, 70, 101}},
   588  				})
   589  
   590  				ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{
   591  					Kind:     "Foo",
   592  					Ancestor: true,
   593  					SortBy: []ds.IndexColumn{
   594  						{Property: "Value"},
   595  					},
   596  				})
   597  
   598  				So(ds.RunInTransaction(c, func(c context.Context) error {
   599  					q = q.Project("Value").Distinct(true)
   600  
   601  					vals := []ds.PropertyMap{}
   602  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   603  					So(len(vals), ShouldEqual, 13)
   604  
   605  					expect := []struct {
   606  						id  int64
   607  						val int64
   608  					}{
   609  						{2, 1},
   610  						{2, 2},
   611  						{2, 3},
   612  						{2, 4},
   613  						{2, 5},
   614  						{2, 6},
   615  						{2, 7},
   616  						{3, 8},
   617  						{3, 9},
   618  						{4, 11},
   619  						{5, 70},
   620  						{4, 100},
   621  						{5, 101},
   622  					}
   623  
   624  					for i, pm := range vals {
   625  						So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val)
   626  						So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble,
   627  							ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id))
   628  					}
   629  
   630  					return nil
   631  				}, nil), ShouldBeNil)
   632  			})
   633  
   634  			Convey("overwrite", func() {
   635  				data := []*Foo{
   636  					{ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}},
   637  					{ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}},
   638  					{ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1, 2}},
   639  					{ID: 5, Parent: root, Value: []int64{1, 70, 101}},
   640  				}
   641  
   642  				_, _, c := mkds(data)
   643  
   644  				q = q.Eq("Value", 2, 3)
   645  
   646  				So(ds.RunInTransaction(c, func(c context.Context) error {
   647  					vals := []*Foo{}
   648  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   649  					So(len(vals), ShouldEqual, 2)
   650  
   651  					So(vals[0], ShouldResemble, data[0])
   652  					So(vals[1], ShouldResemble, data[2])
   653  
   654  					foo2 := &Foo{ID: 2, Parent: root, Value: []int64{2, 3}}
   655  					So(ds.Put(c, foo2), ShouldBeNil)
   656  
   657  					vals = []*Foo{}
   658  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   659  					So(len(vals), ShouldEqual, 2)
   660  
   661  					So(vals[0], ShouldResemble, foo2)
   662  					So(vals[1], ShouldResemble, data[2])
   663  
   664  					foo1 := &Foo{ID: 1, Parent: root, Value: []int64{2, 3}}
   665  					So(ds.Put(c, foo1), ShouldBeNil)
   666  
   667  					vals = []*Foo{}
   668  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   669  					So(len(vals), ShouldEqual, 3)
   670  
   671  					So(vals[0], ShouldResemble, foo1)
   672  					So(vals[1], ShouldResemble, foo2)
   673  					So(vals[2], ShouldResemble, data[2])
   674  
   675  					return nil
   676  				}, nil), ShouldBeNil)
   677  			})
   678  
   679  			projectData := []*Foo{
   680  				{ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}, Sort: []string{"x", "z"}},
   681  				{ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}, Sort: []string{"b"}},
   682  				{ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1, 2}, Sort: []string{"aa", "a"}},
   683  				{ID: 5, Parent: root, Value: []int64{1, 70, 101}, Sort: []string{"c"}},
   684  			}
   685  
   686  			Convey("project+extra orders", func() {
   687  
   688  				_, _, c := mkds(projectData)
   689  				ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{
   690  					Kind:     "Foo",
   691  					Ancestor: true,
   692  					SortBy: []ds.IndexColumn{
   693  						{Property: "Sort", Descending: true},
   694  						{Property: "Value", Descending: true},
   695  					},
   696  				})
   697  
   698  				q = q.Project("Value").Order("-Sort", "-Value").Distinct(true)
   699  				So(ds.RunInTransaction(c, func(c context.Context) error {
   700  					So(ds.Put(c, &Foo{
   701  						ID: 1, Parent: root, Value: []int64{0, 1, 1000},
   702  						Sort: []string{"zz"}}), ShouldBeNil)
   703  
   704  					vals := []ds.PropertyMap{}
   705  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   706  
   707  					expect := []struct {
   708  						id  int64
   709  						val int64
   710  					}{
   711  						{1, 1000},
   712  						{1, 1},
   713  						{1, 0},
   714  						{2, 7},
   715  						{2, 6},
   716  						{2, 5},
   717  						{2, 4},
   718  						{2, 3},
   719  						{2, 2},
   720  						{5, 101},
   721  						{5, 70},
   722  						{3, 9},
   723  						{3, 8},
   724  						{4, 100},
   725  						{4, 11},
   726  					}
   727  
   728  					for i, pm := range vals {
   729  						So(pm.Slice("Value")[0].Value(), ShouldEqual, expect[i].val)
   730  						So(ds.GetMetaDefault(pm, "key", nil), ShouldResemble,
   731  							ds.MakeKey(c, "Parent", 1, "Foo", expect[i].id))
   732  					}
   733  
   734  					return nil
   735  				}, nil), ShouldBeNil)
   736  			})
   737  
   738  			Convey("buffered entity sorts before ineq, but after first parent entity", func() {
   739  				// If we got this wrong, we'd see Foo,3 come before Foo,2. This might
   740  				// happen because we calculate the comparison string for each entity
   741  				// based on the whole entity, but we forgot to limit the comparison
   742  				// string generation by the inequality criteria.
   743  				data := []*Foo{
   744  					{ID: 2, Parent: root, Value: []int64{2, 3, 5, 6}, Sort: []string{"z"}},
   745  				}
   746  
   747  				_, _, c := mkds(data)
   748  				ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{
   749  					Kind:     "Foo",
   750  					Ancestor: true,
   751  					SortBy: []ds.IndexColumn{
   752  						{Property: "Value"},
   753  					},
   754  				})
   755  
   756  				q = q.Gt("Value", 2).Limit(2)
   757  
   758  				So(ds.RunInTransaction(c, func(c context.Context) error {
   759  					foo1 := &Foo{ID: 3, Parent: root, Value: []int64{0, 2, 3, 4}}
   760  					So(ds.Put(c, foo1), ShouldBeNil)
   761  
   762  					vals := []*Foo{}
   763  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   764  					So(len(vals), ShouldEqual, 2)
   765  
   766  					So(vals[0], ShouldResemble, data[0])
   767  					So(vals[1], ShouldResemble, foo1)
   768  
   769  					return nil
   770  				}, nil), ShouldBeNil)
   771  			})
   772  
   773  			Convey("keysOnly+extra orders", func() {
   774  				_, _, c := mkds(projectData)
   775  				ds.GetTestable(c).AddIndexes(&ds.IndexDefinition{
   776  					Kind:     "Foo",
   777  					Ancestor: true,
   778  					SortBy: []ds.IndexColumn{
   779  						{Property: "Sort"},
   780  					},
   781  				})
   782  
   783  				q = q.Order("Sort").KeysOnly(true)
   784  
   785  				So(ds.RunInTransaction(c, func(c context.Context) error {
   786  					So(ds.Put(c, &Foo{
   787  						ID: 1, Parent: root, Value: []int64{0, 1, 1000},
   788  						Sort: []string{"x", "zz"}}), ShouldBeNil)
   789  
   790  					So(ds.Put(c, &Foo{
   791  						ID: 2, Parent: root, Value: []int64{0, 1, 1000},
   792  						Sort: []string{"zz", "zzz", "zzzz"}}), ShouldBeNil)
   793  
   794  					vals := []*ds.Key{}
   795  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   796  					So(len(vals), ShouldEqual, 5)
   797  
   798  					kc := ds.GetKeyContext(c)
   799  					So(vals, ShouldResemble, []*ds.Key{
   800  						kc.MakeKey("Parent", 1, "Foo", 4),
   801  						kc.MakeKey("Parent", 1, "Foo", 3),
   802  						kc.MakeKey("Parent", 1, "Foo", 5),
   803  						kc.MakeKey("Parent", 1, "Foo", 1),
   804  						kc.MakeKey("Parent", 1, "Foo", 2),
   805  					})
   806  
   807  					return nil
   808  				}, nil), ShouldBeNil)
   809  			})
   810  
   811  			Convey("query accross nested transactions", func() {
   812  				_, _, c := mkds(projectData)
   813  				q = q.Eq("Value", 2, 3)
   814  
   815  				foo1 := &Foo{ID: 1, Parent: root, Value: []int64{2, 3}}
   816  				foo7 := &Foo{ID: 7, Parent: root, Value: []int64{2, 3}}
   817  
   818  				So(ds.RunInTransaction(c, func(c context.Context) error {
   819  					So(ds.Put(c, foo1), ShouldBeNil)
   820  
   821  					vals := []*Foo{}
   822  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   823  					So(vals, ShouldResemble, []*Foo{foo1, projectData[0], projectData[2]})
   824  
   825  					So(ds.RunInTransaction(c, func(c context.Context) error {
   826  						vals := []*Foo{}
   827  						So(ds.GetAll(c, q, &vals), ShouldBeNil)
   828  						So(vals, ShouldResemble, []*Foo{foo1, projectData[0], projectData[2]})
   829  
   830  						So(ds.Delete(c, ds.MakeKey(c, "Parent", 1, "Foo", 4)), ShouldBeNil)
   831  						So(ds.Put(c, foo7), ShouldBeNil)
   832  
   833  						vals = []*Foo{}
   834  						So(ds.GetAll(c, q, &vals), ShouldBeNil)
   835  						So(vals, ShouldResemble, []*Foo{foo1, projectData[0], foo7})
   836  
   837  						return nil
   838  					}, nil), ShouldBeNil)
   839  
   840  					vals = []*Foo{}
   841  					So(ds.GetAll(c, q, &vals), ShouldBeNil)
   842  					So(vals, ShouldResemble, []*Foo{foo1, projectData[0], foo7})
   843  
   844  					return nil
   845  				}, nil), ShouldBeNil)
   846  
   847  				vals := []*Foo{}
   848  				So(ds.GetAll(c, q, &vals), ShouldBeNil)
   849  				So(vals, ShouldResemble, []*Foo{foo1, projectData[0], foo7})
   850  
   851  			})
   852  
   853  			Convey("start transaction from inside query", func() {
   854  				_, _, c := mkds(projectData)
   855  				So(ds.RunInTransaction(c, func(c context.Context) error {
   856  					q := ds.NewQuery("Foo").Ancestor(root)
   857  					return ds.Run(c, q, func(pm ds.PropertyMap) {
   858  						So(ds.RunInTransaction(c, func(c context.Context) error {
   859  							pm["Value"] = append(pm.Slice("Value"), ds.MkProperty("wat"))
   860  							return ds.Put(c, pm)
   861  						}, nil), ShouldBeNil)
   862  					})
   863  				}, nil), ShouldBeNil)
   864  
   865  				So(ds.Run(c, ds.NewQuery("Foo"), func(pm ds.PropertyMap) {
   866  					val := pm.Slice("Value")
   867  					So(val[len(val)-1].Value(), ShouldResemble, "wat")
   868  				}), ShouldBeNil)
   869  			})
   870  
   871  		})
   872  
   873  	})
   874  
   875  }
   876  
   877  func TestRegressions(t *testing.T) {
   878  	Convey("Regression tests", t, func() {
   879  		Convey("can remove namespace from txnBuf filter", func() {
   880  			c := info.MustNamespace(memory.Use(context.Background()), "foobar")
   881  			So(info.GetNamespace(c), ShouldEqual, "foobar")
   882  			ds.RunInTransaction(FilterRDS(c), func(c context.Context) error {
   883  				So(info.GetNamespace(c), ShouldEqual, "foobar")
   884  				c = ds.WithoutTransaction(info.MustNamespace(c, ""))
   885  				So(info.GetNamespace(c), ShouldEqual, "")
   886  				return nil
   887  			}, nil)
   888  		})
   889  	})
   890  }