Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package memory | 5 package memory |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "errors" | |
| 8 "fmt" | 9 "fmt" |
| 9 "testing" | 10 "testing" |
| 10 "time" | 11 "time" |
| 11 | 12 |
| 12 » dsS "github.com/luci/gae/service/datastore" | 13 » ds "github.com/luci/gae/service/datastore" |
| 13 "github.com/luci/gae/service/datastore/serialize" | 14 "github.com/luci/gae/service/datastore/serialize" |
| 14 infoS "github.com/luci/gae/service/info" | 15 infoS "github.com/luci/gae/service/info" |
| 16 | |
| 17 "golang.org/x/net/context" | |
| 18 | |
| 15 . "github.com/luci/luci-go/common/testing/assertions" | 19 . "github.com/luci/luci-go/common/testing/assertions" |
| 16 . "github.com/smartystreets/goconvey/convey" | 20 . "github.com/smartystreets/goconvey/convey" |
| 17 "golang.org/x/net/context" | |
| 18 ) | 21 ) |
| 19 | 22 |
| 20 type MetaGroup struct { | 23 type MetaGroup struct { |
| 21 » _id int64 `gae:"$id,1"` | 24 » _id int64 `gae:"$id,1"` |
| 22 » _kind string `gae:"$kind,__entity_group__"` | 25 » _kind string `gae:"$kind,__entity_group__"` |
| 23 » Parent *dsS.Key `gae:"$parent"` | 26 » Parent *ds.Key `gae:"$parent"` |
| 24 | 27 |
| 25 Version int64 `gae:"__version__"` | 28 Version int64 `gae:"__version__"` |
| 26 } | 29 } |
| 27 | 30 |
| 28 func testGetMeta(c context.Context, k *dsS.Key) int64 { | 31 func testGetMeta(c context.Context, k *ds.Key) int64 { |
| 29 » ds := dsS.Get(c) | |
| 30 mg := &MetaGroup{Parent: k.Root()} | 32 mg := &MetaGroup{Parent: k.Root()} |
| 31 » if err := ds.Get(mg); err != nil { | 33 » if err := ds.Get(c, mg); err != nil { |
| 32 panic(err) | 34 panic(err) |
| 33 } | 35 } |
| 34 return mg.Version | 36 return mg.Version |
| 35 } | 37 } |
| 36 | 38 |
| 37 var pls = dsS.GetPLS | 39 var pls = ds.GetPLS |
| 38 | 40 |
| 39 type Foo struct { | 41 type Foo struct { |
| 40 » ID int64 `gae:"$id"` | 42 » ID int64 `gae:"$id"` |
| 41 » Parent *dsS.Key `gae:"$parent"` | 43 » Parent *ds.Key `gae:"$parent"` |
| 42 | 44 |
| 43 Val int | 45 Val int |
| 44 Name string | 46 Name string |
| 45 } | 47 } |
| 46 | 48 |
| 47 func TestDatastoreSingleReadWriter(t *testing.T) { | 49 func TestDatastoreSingleReadWriter(t *testing.T) { |
| 48 t.Parallel() | 50 t.Parallel() |
| 49 | 51 |
| 50 Convey("Datastore single reads and writes", t, func() { | 52 Convey("Datastore single reads and writes", t, func() { |
| 51 c := Use(context.Background()) | 53 c := Use(context.Background()) |
| 52 » » ds := dsS.Get(c) | 54 » » So(ds.Raw(c), ShouldNotBeNil) |
| 53 » » So(ds, ShouldNotBeNil) | |
| 54 | 55 |
| 55 Convey("getting objects that DNE is an error", func() { | 56 Convey("getting objects that DNE is an error", func() { |
| 56 » » » So(ds.Get(&Foo{ID: 1}), ShouldEqual, dsS.ErrNoSuchEntity ) | 57 » » » So(ds.Get(c, &Foo{ID: 1}), ShouldEqual, ds.ErrNoSuchEnti ty) |
| 57 }) | 58 }) |
| 58 | 59 |
| 59 Convey("bad namespaces fail", func() { | 60 Convey("bad namespaces fail", func() { |
| 60 » » » _, err := infoS.Get(c).Namespace("$$blzyall") | 61 » » » _, err := infoS.Namespace(c, "$$blzyall") |
| 61 So(err.Error(), ShouldContainSubstring, "namespace \"$$b lzyall\" does not match") | 62 So(err.Error(), ShouldContainSubstring, "namespace \"$$b lzyall\" does not match") |
| 62 }) | 63 }) |
| 63 | 64 |
| 64 Convey("Can Put stuff", func() { | 65 Convey("Can Put stuff", func() { |
| 65 // with an incomplete key! | 66 // with an incomplete key! |
| 66 f := &Foo{Val: 10} | 67 f := &Foo{Val: 10} |
| 67 » » » So(ds.Put(f), ShouldBeNil) | 68 » » » So(ds.Put(c, f), ShouldBeNil) |
| 68 » » » k := ds.KeyForObj(f) | 69 » » » k := ds.KeyForObj(c, f) |
| 69 So(k.String(), ShouldEqual, "dev~app::/Foo,1") | 70 So(k.String(), ShouldEqual, "dev~app::/Foo,1") |
| 70 | 71 |
| 71 Convey("and Get it back", func() { | 72 Convey("and Get it back", func() { |
| 72 newFoo := &Foo{ID: 1} | 73 newFoo := &Foo{ID: 1} |
| 73 » » » » So(ds.Get(newFoo), ShouldBeNil) | 74 » » » » So(ds.Get(c, newFoo), ShouldBeNil) |
| 74 So(newFoo, ShouldResemble, f) | 75 So(newFoo, ShouldResemble, f) |
| 75 | 76 |
| 76 Convey("but it's hidden from a different namespa ce", func() { | 77 Convey("but it's hidden from a different namespa ce", func() { |
| 77 » » » » » c, err := infoS.Get(c).Namespace("whomba t") | 78 » » » » » c, err := infoS.Namespace(c, "whombat") |
| 78 So(err, ShouldBeNil) | 79 So(err, ShouldBeNil) |
| 79 » » » » » ds = dsS.Get(c) | 80 » » » » » So(ds.Get(c, f), ShouldEqual, ds.ErrNoSu chEntity) |
| 80 » » » » » So(ds.Get(f), ShouldEqual, dsS.ErrNoSuch Entity) | |
| 81 }) | 81 }) |
| 82 | 82 |
| 83 Convey("and we can Delete it", func() { | 83 Convey("and we can Delete it", func() { |
| 84 » » » » » So(ds.Delete(k), ShouldBeNil) | 84 » » » » » So(ds.Delete(c, k), ShouldBeNil) |
| 85 » » » » » So(ds.Get(newFoo), ShouldEqual, dsS.ErrN oSuchEntity) | 85 » » » » » So(ds.Get(c, newFoo), ShouldEqual, ds.Er rNoSuchEntity) |
| 86 }) | 86 }) |
| 87 | 87 |
| 88 }) | 88 }) |
| 89 Convey("Deleteing with a bogus key is bad", func() { | 89 Convey("Deleteing with a bogus key is bad", func() { |
| 90 » » » » So(ds.Delete(ds.NewKey("Foo", "wat", 100, nil)), ShouldEqual, dsS.ErrInvalidKey) | 90 » » » » So(ds.Delete(c, ds.NewKey(c, "Foo", "wat", 100, nil)), ShouldEqual, ds.ErrInvalidKey) |
| 91 }) | 91 }) |
| 92 Convey("Deleteing a DNE entity is fine", func() { | 92 Convey("Deleteing a DNE entity is fine", func() { |
| 93 » » » » So(ds.Delete(ds.NewKey("Foo", "wat", 0, nil)), S houldBeNil) | 93 » » » » So(ds.Delete(c, ds.NewKey(c, "Foo", "wat", 0, ni l)), ShouldBeNil) |
| 94 }) | 94 }) |
| 95 | 95 |
| 96 Convey("Deleting entities from a nonexistant namespace w orks", func() { | 96 Convey("Deleting entities from a nonexistant namespace w orks", func() { |
| 97 » » » » aid := infoS.Get(c).FullyQualifiedAppID() | 97 » » » » c := infoS.MustNamespace(c, "noexist") |
| 98 » » » » keys := make([]*dsS.Key, 10) | 98 » » » » keys := make([]*ds.Key, 10) |
| 99 for i := range keys { | 99 for i := range keys { |
| 100 » » » » » keys[i] = ds.MakeKey(aid, "noexist", "Ki nd", i+1) | 100 » » » » » keys[i] = ds.MakeKey(c, "Kind", i+1) |
| 101 } | 101 } |
| 102 » » » » So(ds.DeleteMulti(keys), ShouldBeNil) | 102 » » » » So(ds.Delete(c, keys), ShouldBeNil) |
| 103 count := 0 | 103 count := 0 |
| 104 » » » » So(ds.Raw().DeleteMulti(keys, func(err error) er ror { | 104 » » » » So(ds.Raw(c).DeleteMulti(keys, func(err error) e rror { |
| 105 count++ | 105 count++ |
| 106 So(err, ShouldBeNil) | 106 So(err, ShouldBeNil) |
| 107 return nil | 107 return nil |
| 108 }), ShouldBeNil) | 108 }), ShouldBeNil) |
| 109 So(count, ShouldEqual, len(keys)) | 109 So(count, ShouldEqual, len(keys)) |
| 110 }) | 110 }) |
| 111 | 111 |
| 112 Convey("with multiple puts", func() { | 112 Convey("with multiple puts", func() { |
| 113 So(testGetMeta(c, k), ShouldEqual, 1) | 113 So(testGetMeta(c, k), ShouldEqual, 1) |
| 114 | 114 |
| 115 foos := make([]Foo, 10) | 115 foos := make([]Foo, 10) |
| 116 for i := range foos { | 116 for i := range foos { |
| 117 foos[i].Val = 10 | 117 foos[i].Val = 10 |
| 118 foos[i].Parent = k | 118 foos[i].Parent = k |
| 119 } | 119 } |
| 120 » » » » So(ds.PutMulti(foos), ShouldBeNil) | 120 » » » » So(ds.Put(c, foos), ShouldBeNil) |
| 121 So(testGetMeta(c, k), ShouldEqual, 11) | 121 So(testGetMeta(c, k), ShouldEqual, 11) |
| 122 | 122 |
| 123 » » » » keys := make([]*dsS.Key, len(foos)) | 123 » » » » keys := make([]*ds.Key, len(foos)) |
| 124 for i, f := range foos { | 124 for i, f := range foos { |
| 125 » » » » » keys[i] = ds.KeyForObj(&f) | 125 » » » » » keys[i] = ds.KeyForObj(c, &f) |
| 126 } | 126 } |
| 127 | 127 |
| 128 Convey("ensure that group versions persist acros s deletes", func() { | 128 Convey("ensure that group versions persist acros s deletes", func() { |
| 129 » » » » » So(ds.DeleteMulti(append(keys, k)), Shou ldBeNil) | 129 » » » » » So(ds.Delete(c, append(keys, k)), Should BeNil) |
| 130 | 130 |
| 131 » » » » » ds.Testable().CatchupIndexes() | 131 » » » » » ds.GetTestable(c).CatchupIndexes() |
| 132 | 132 |
| 133 count := 0 | 133 count := 0 |
| 134 » » » » » So(ds.Run(dsS.NewQuery(""), func(_ *dsS. Key) { | 134 » » » » » So(ds.Run(c, ds.NewQuery(""), func(_ *ds .Key) { |
| 135 count++ | 135 count++ |
| 136 }), ShouldBeNil) | 136 }), ShouldBeNil) |
| 137 So(count, ShouldEqual, 3) | 137 So(count, ShouldEqual, 3) |
| 138 | 138 |
| 139 So(testGetMeta(c, k), ShouldEqual, 22) | 139 So(testGetMeta(c, k), ShouldEqual, 22) |
| 140 | 140 |
| 141 » » » » » So(ds.Put(&Foo{ID: 1}), ShouldBeNil) | 141 » » » » » So(ds.Put(c, &Foo{ID: 1}), ShouldBeNil) |
| 142 So(testGetMeta(c, k), ShouldEqual, 23) | 142 So(testGetMeta(c, k), ShouldEqual, 23) |
| 143 }) | 143 }) |
| 144 | 144 |
| 145 Convey("can Get", func() { | 145 Convey("can Get", func() { |
| 146 » » » » » vals := make([]dsS.PropertyMap, len(keys )) | 146 » » » » » vals := make([]ds.PropertyMap, len(keys) ) |
| 147 for i := range vals { | 147 for i := range vals { |
| 148 » » » » » » vals[i] = dsS.PropertyMap{} | 148 » » » » » » vals[i] = ds.PropertyMap{} |
| 149 So(vals[i].SetMeta("key", keys[i ]), ShouldBeTrue) | 149 So(vals[i].SetMeta("key", keys[i ]), ShouldBeTrue) |
| 150 } | 150 } |
| 151 » » » » » So(ds.GetMulti(vals), ShouldBeNil) | 151 » » » » » So(ds.Get(c, vals), ShouldBeNil) |
| 152 | 152 |
| 153 for i, val := range vals { | 153 for i, val := range vals { |
| 154 » » » » » » So(val, ShouldResemble, dsS.Prop ertyMap{ | 154 » » » » » » So(val, ShouldResemble, ds.Prope rtyMap{ |
| 155 » » » » » » » "Val": {dsS.MkProperty( 10)}, | 155 » » » » » » » "Val": {ds.MkProperty(1 0)}, |
| 156 » » » » » » » "Name": {dsS.MkProperty( "")}, | 156 » » » » » » » "Name": {ds.MkProperty(" ")}, |
| 157 » » » » » » » "$key": {dsS.MkPropertyN I(keys[i])}, | 157 » » » » » » » "$key": {ds.MkPropertyNI (keys[i])}, |
| 158 }) | 158 }) |
| 159 } | 159 } |
| 160 }) | 160 }) |
| 161 | 161 |
| 162 }) | 162 }) |
| 163 | 163 |
| 164 Convey("allocating ids prevents their use", func() { | 164 Convey("allocating ids prevents their use", func() { |
| 165 » » » » keys := ds.NewIncompleteKeys(100, "Foo", nil) | 165 » » » » keys := ds.NewIncompleteKeys(c, 100, "Foo", nil) |
| 166 » » » » So(ds.AllocateIDs(keys), ShouldBeNil) | 166 » » » » So(ds.AllocateIDs(c, keys), ShouldBeNil) |
| 167 So(len(keys), ShouldEqual, 100) | 167 So(len(keys), ShouldEqual, 100) |
| 168 | 168 |
| 169 // Assert that none of our keys share the same I D. | 169 // Assert that none of our keys share the same I D. |
| 170 ids := make(map[int64]struct{}) | 170 ids := make(map[int64]struct{}) |
| 171 for _, k := range keys { | 171 for _, k := range keys { |
| 172 ids[k.IntID()] = struct{}{} | 172 ids[k.IntID()] = struct{}{} |
| 173 } | 173 } |
| 174 So(len(ids), ShouldEqual, len(keys)) | 174 So(len(ids), ShouldEqual, len(keys)) |
| 175 | 175 |
| 176 // Put a new object and ensure that it is alloca ted an unused ID. | 176 // Put a new object and ensure that it is alloca ted an unused ID. |
| 177 f := &Foo{Val: 10} | 177 f := &Foo{Val: 10} |
| 178 » » » » So(ds.Put(f), ShouldBeNil) | 178 » » » » So(ds.Put(c, f), ShouldBeNil) |
| 179 » » » » k := ds.KeyForObj(f) | 179 » » » » k := ds.KeyForObj(c, f) |
| 180 So(k.String(), ShouldEqual, "dev~app::/Foo,102") | 180 So(k.String(), ShouldEqual, "dev~app::/Foo,102") |
| 181 | 181 |
| 182 _, ok := ids[k.IntID()] | 182 _, ok := ids[k.IntID()] |
| 183 So(ok, ShouldBeFalse) | 183 So(ok, ShouldBeFalse) |
| 184 }) | 184 }) |
| 185 }) | 185 }) |
| 186 | 186 |
| 187 Convey("implements DSTransactioner", func() { | 187 Convey("implements DSTransactioner", func() { |
| 188 Convey("Put", func() { | 188 Convey("Put", func() { |
| 189 f := &Foo{Val: 10} | 189 f := &Foo{Val: 10} |
| 190 » » » » So(ds.Put(f), ShouldBeNil) | 190 » » » » So(ds.Put(c, f), ShouldBeNil) |
| 191 » » » » k := ds.KeyForObj(f) | 191 » » » » k := ds.KeyForObj(c, f) |
| 192 So(k.String(), ShouldEqual, "dev~app::/Foo,1") | 192 So(k.String(), ShouldEqual, "dev~app::/Foo,1") |
| 193 | 193 |
| 194 Convey("can describe its transaction state", fun c() { | |
| 195 So(ds.CurrentTransaction(c), ShouldBeNil ) | |
|
dnj
2016/09/01 15:25:40
This test makes me happy.
iannucci
2016/09/16 01:01:13
:D
| |
| 196 | |
| 197 err := ds.RunInTransaction(c, func(c con text.Context) error { | |
| 198 So(ds.CurrentTransaction(c), Sho uldNotBeNil) | |
| 199 | |
| 200 // Can reset to nil. | |
| 201 nc := ds.WithTransaction(c, nil) | |
| 202 So(ds.CurrentTransaction(nc), Sh ouldBeNil) | |
| 203 return nil | |
| 204 }, nil) | |
| 205 So(err, ShouldBeNil) | |
| 206 }) | |
| 207 | |
| 194 Convey("can Put new entity groups", func() { | 208 Convey("can Put new entity groups", func() { |
| 195 » » » » » err := ds.RunInTransaction(func(c contex t.Context) error { | 209 » » » » » err := ds.RunInTransaction(c, func(c con text.Context) error { |
| 196 » » » » » » ds := dsS.Get(c) | |
| 197 | |
| 198 f := &Foo{Val: 100} | 210 f := &Foo{Val: 100} |
| 199 » » » » » » So(ds.Put(f), ShouldBeNil) | 211 » » » » » » So(ds.Put(c, f), ShouldBeNil) |
| 200 So(f.ID, ShouldEqual, 2) | 212 So(f.ID, ShouldEqual, 2) |
| 201 | 213 |
| 202 f.ID = 0 | 214 f.ID = 0 |
| 203 f.Val = 200 | 215 f.Val = 200 |
| 204 » » » » » » So(ds.Put(f), ShouldBeNil) | 216 » » » » » » So(ds.Put(c, f), ShouldBeNil) |
| 205 So(f.ID, ShouldEqual, 3) | 217 So(f.ID, ShouldEqual, 3) |
| 206 | 218 |
| 207 return nil | 219 return nil |
| 208 » » » » » }, &dsS.TransactionOptions{XG: true}) | 220 » » » » » }, &ds.TransactionOptions{XG: true}) |
| 209 So(err, ShouldBeNil) | 221 So(err, ShouldBeNil) |
| 210 | 222 |
| 211 f := &Foo{ID: 2} | 223 f := &Foo{ID: 2} |
| 212 » » » » » So(ds.Get(f), ShouldBeNil) | 224 » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 213 So(f.Val, ShouldEqual, 100) | 225 So(f.Val, ShouldEqual, 100) |
| 214 | 226 |
| 215 f.ID = 3 | 227 f.ID = 3 |
| 216 » » » » » So(ds.Get(f), ShouldBeNil) | 228 » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 217 So(f.Val, ShouldEqual, 200) | 229 So(f.Val, ShouldEqual, 200) |
| 218 }) | 230 }) |
| 219 | 231 |
| 220 Convey("can Put new entities in a current group" , func() { | 232 Convey("can Put new entities in a current group" , func() { |
| 221 » » » » » err := ds.RunInTransaction(func(c contex t.Context) error { | 233 » » » » » err := ds.RunInTransaction(c, func(c con text.Context) error { |
| 222 » » » » » » ds := dsS.Get(c) | |
| 223 | |
| 224 f := &Foo{Val: 100, Parent: k} | 234 f := &Foo{Val: 100, Parent: k} |
| 225 » » » » » » So(ds.Put(f), ShouldBeNil) | 235 » » » » » » So(ds.Put(c, f), ShouldBeNil) |
| 226 » » » » » » So(ds.KeyForObj(f).String(), Sho uldEqual, "dev~app::/Foo,1/Foo,1") | 236 » » » » » » So(ds.KeyForObj(c, f).String(), ShouldEqual, "dev~app::/Foo,1/Foo,1") |
| 227 | 237 |
| 228 f.ID = 0 | 238 f.ID = 0 |
| 229 f.Val = 200 | 239 f.Val = 200 |
| 230 » » » » » » So(ds.Put(f), ShouldBeNil) | 240 » » » » » » So(ds.Put(c, f), ShouldBeNil) |
| 231 » » » » » » So(ds.KeyForObj(f).String(), Sho uldEqual, "dev~app::/Foo,1/Foo,2") | 241 » » » » » » So(ds.KeyForObj(c, f).String(), ShouldEqual, "dev~app::/Foo,1/Foo,2") |
| 232 | 242 |
| 233 return nil | 243 return nil |
| 234 }, nil) | 244 }, nil) |
| 235 So(err, ShouldBeNil) | 245 So(err, ShouldBeNil) |
| 236 | 246 |
| 237 f := &Foo{ID: 1, Parent: k} | 247 f := &Foo{ID: 1, Parent: k} |
| 238 » » » » » So(ds.Get(f), ShouldBeNil) | 248 » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 239 So(f.Val, ShouldEqual, 100) | 249 So(f.Val, ShouldEqual, 100) |
| 240 | 250 |
| 241 f.ID = 2 | 251 f.ID = 2 |
| 242 » » » » » So(ds.Get(f), ShouldBeNil) | 252 » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 243 So(f.Val, ShouldEqual, 200) | 253 So(f.Val, ShouldEqual, 200) |
| 244 }) | 254 }) |
| 245 | 255 |
| 246 Convey("Deletes work too", func() { | 256 Convey("Deletes work too", func() { |
| 247 » » » » » err := ds.RunInTransaction(func(c contex t.Context) error { | 257 » » » » » err := ds.RunInTransaction(c, func(c con text.Context) error { |
| 248 » » » » » » return dsS.Get(c).Delete(k) | 258 » » » » » » return ds.Delete(c, k) |
| 249 }, nil) | 259 }, nil) |
| 250 So(err, ShouldBeNil) | 260 So(err, ShouldBeNil) |
| 251 » » » » » So(ds.Get(&Foo{ID: 1}), ShouldEqual, dsS .ErrNoSuchEntity) | 261 » » » » » So(ds.Get(c, &Foo{ID: 1}), ShouldEqual, ds.ErrNoSuchEntity) |
| 252 }) | 262 }) |
| 253 | 263 |
| 254 Convey("A Get counts against your group count", func() { | 264 Convey("A Get counts against your group count", func() { |
| 255 » » » » » err := ds.RunInTransaction(func(c contex t.Context) error { | 265 » » » » » err := ds.RunInTransaction(c, func(c con text.Context) error { |
| 256 » » » » » » ds := dsS.Get(c) | 266 » » » » » » pm := ds.PropertyMap{} |
| 257 | 267 » » » » » » So(pm.SetMeta("key", ds.NewKey(c , "Foo", "", 20, nil)), ShouldBeTrue) |
| 258 » » » » » » pm := dsS.PropertyMap{} | 268 » » » » » » So(ds.Get(c, pm), ShouldEqual, d s.ErrNoSuchEntity) |
| 259 » » » » » » So(pm.SetMeta("key", ds.NewKey(" Foo", "", 20, nil)), ShouldBeTrue) | |
| 260 » » » » » » So(ds.Get(pm), ShouldEqual, dsS. ErrNoSuchEntity) | |
| 261 | 269 |
| 262 So(pm.SetMeta("key", k), ShouldB eTrue) | 270 So(pm.SetMeta("key", k), ShouldB eTrue) |
| 263 » » » » » » So(ds.Get(pm).Error(), ShouldCon tainSubstring, "cross-group") | 271 » » » » » » So(ds.Get(c, pm).Error(), Should ContainSubstring, "cross-group") |
| 264 return nil | 272 return nil |
| 265 }, nil) | 273 }, nil) |
| 266 So(err, ShouldBeNil) | 274 So(err, ShouldBeNil) |
| 267 }) | 275 }) |
| 268 | 276 |
| 269 Convey("Get takes a snapshot", func() { | 277 Convey("Get takes a snapshot", func() { |
| 270 » » » » » err := ds.RunInTransaction(func(c contex t.Context) error { | 278 » » » » » err := ds.RunInTransaction(c, func(c con text.Context) error { |
| 271 » » » » » » ds := dsS.Get(c) | 279 » » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 272 | |
| 273 » » » » » » So(ds.Get(f), ShouldBeNil) | |
| 274 So(f.Val, ShouldEqual, 10) | 280 So(f.Val, ShouldEqual, 10) |
| 275 | 281 |
| 276 // Don't ever do this in a real program unless you want to guarantee | 282 // Don't ever do this in a real program unless you want to guarantee |
| 277 // a failed transaction :) | 283 // a failed transaction :) |
| 278 f.Val = 11 | 284 f.Val = 11 |
| 279 » » » » » » So(dsS.GetNoTxn(c).Put(f), Shoul dBeNil) | 285 » » » » » » So(ds.Put(ds.WithTransaction(c, nil), f), ShouldBeNil) |
| 280 | 286 |
| 281 » » » » » » So(ds.Get(f), ShouldBeNil) | 287 » » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 282 So(f.Val, ShouldEqual, 10) | 288 So(f.Val, ShouldEqual, 10) |
| 283 | 289 |
| 284 return nil | 290 return nil |
| 285 }, nil) | 291 }, nil) |
| 286 So(err, ShouldBeNil) | 292 So(err, ShouldBeNil) |
| 287 | 293 |
| 288 f := &Foo{ID: 1} | 294 f := &Foo{ID: 1} |
| 289 » » » » » So(ds.Get(f), ShouldBeNil) | 295 » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 290 So(f.Val, ShouldEqual, 11) | 296 So(f.Val, ShouldEqual, 11) |
| 291 }) | 297 }) |
| 292 | 298 |
| 293 Convey("and snapshots are consistent even after Puts", func() { | 299 Convey("and snapshots are consistent even after Puts", func() { |
| 294 » » » » » err := ds.RunInTransaction(func(c contex t.Context) error { | 300 » » » » » err := ds.RunInTransaction(c, func(c con text.Context) error { |
| 295 » » » » » » ds := dsS.Get(c) | |
| 296 | |
| 297 f := &Foo{ID: 1} | 301 f := &Foo{ID: 1} |
| 298 » » » » » » So(ds.Get(f), ShouldBeNil) | 302 » » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 299 So(f.Val, ShouldEqual, 10) | 303 So(f.Val, ShouldEqual, 10) |
| 300 | 304 |
| 301 // Don't ever do this in a real program unless you want to guarantee | 305 // Don't ever do this in a real program unless you want to guarantee |
| 302 // a failed transaction :) | 306 // a failed transaction :) |
| 303 f.Val = 11 | 307 f.Val = 11 |
| 304 » » » » » » So(dsS.GetNoTxn(c).Put(f), Shoul dBeNil) | 308 » » » » » » So(ds.Put(ds.WithTransaction(c, nil), f), ShouldBeNil) |
| 305 | 309 |
| 306 » » » » » » So(ds.Get(f), ShouldBeNil) | 310 » » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 307 So(f.Val, ShouldEqual, 10) | 311 So(f.Val, ShouldEqual, 10) |
| 308 | 312 |
| 309 f.Val = 20 | 313 f.Val = 20 |
| 310 » » » » » » So(ds.Put(f), ShouldBeNil) | 314 » » » » » » So(ds.Put(c, f), ShouldBeNil) |
| 311 | 315 |
| 312 » » » » » » So(ds.Get(f), ShouldBeNil) | 316 » » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 313 So(f.Val, ShouldEqual, 10) // st ill gets 10 | 317 So(f.Val, ShouldEqual, 10) // st ill gets 10 |
| 314 | 318 |
| 315 return nil | 319 return nil |
| 316 » » » » » }, &dsS.TransactionOptions{Attempts: 1}) | 320 » » » » » }, &ds.TransactionOptions{Attempts: 1}) |
| 317 So(err.Error(), ShouldContainSubstring, "concurrent") | 321 So(err.Error(), ShouldContainSubstring, "concurrent") |
| 318 | 322 |
| 319 f := &Foo{ID: 1} | 323 f := &Foo{ID: 1} |
| 320 » » » » » So(ds.Get(f), ShouldBeNil) | 324 » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 321 So(f.Val, ShouldEqual, 11) | 325 So(f.Val, ShouldEqual, 11) |
| 322 }) | 326 }) |
| 323 | 327 |
| 324 Convey("Reusing a transaction context is bad new s", func() { | 328 Convey("Reusing a transaction context is bad new s", func() { |
| 325 » » » » » txnDS := dsS.Interface(nil) | 329 » » » » » var txnCtx context.Context |
| 326 » » » » » err := ds.RunInTransaction(func(c contex t.Context) error { | 330 » » » » » err := ds.RunInTransaction(c, func(c con text.Context) error { |
| 327 » » » » » » txnDS = dsS.Get(c) | 331 » » » » » » txnCtx = c |
| 328 » » » » » » So(txnDS.Get(f), ShouldBeNil) | 332 » » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 329 return nil | 333 return nil |
| 330 }, nil) | 334 }, nil) |
| 331 So(err, ShouldBeNil) | 335 So(err, ShouldBeNil) |
| 332 » » » » » So(txnDS.Get(f).Error(), ShouldContainSu bstring, "expired") | 336 » » » » » So(ds.Get(txnCtx, f).Error(), ShouldCont ainSubstring, "expired") |
| 333 }) | 337 }) |
| 334 | 338 |
| 335 Convey("Nested transactions are rejected", func( ) { | 339 Convey("Nested transactions are rejected", func( ) { |
| 336 » » » » » err := ds.RunInTransaction(func(c contex t.Context) error { | 340 » » » » » err := ds.RunInTransaction(c, func(c con text.Context) error { |
| 337 » » » » » » err := dsS.Get(c).RunInTransacti on(func(c context.Context) error { | 341 » » » » » » err := ds.RunInTransaction(c, fu nc(c context.Context) error { |
| 338 panic("noooo") | 342 panic("noooo") |
| 339 }, nil) | 343 }, nil) |
| 340 So(err.Error(), ShouldContainSub string, "nested transactions") | 344 So(err.Error(), ShouldContainSub string, "nested transactions") |
| 341 return nil | 345 return nil |
| 342 }, nil) | 346 }, nil) |
| 343 So(err, ShouldBeNil) | 347 So(err, ShouldBeNil) |
| 344 }) | 348 }) |
| 345 | 349 |
| 350 Convey("Transactions can be escaped.", func() { | |
| 351 testError := errors.New("test error") | |
| 352 noTxnPM := ds.PropertyMap{ | |
| 353 "$kind": []ds.Property{ds.MkProp erty("Test")}, | |
| 354 "$id": []ds.Property{ds.MkProp erty("no txn")}} | |
| 355 | |
| 356 err := ds.RunInTransaction(c, func(c con text.Context) error { | |
| 357 So(ds.CurrentTransaction(c), Sho uldNotBeNil) | |
| 358 | |
| 359 pmap := ds.PropertyMap{ | |
| 360 "$kind": []ds.Property{d s.MkProperty("Test")}, | |
| 361 "$id": []ds.Property{d s.MkProperty("quux")}} | |
| 362 if err := ds.Put(c, pmap); err ! = nil { | |
| 363 return err | |
| 364 } | |
| 365 | |
| 366 // Put an entity outside of the transaction so we can confirm that | |
| 367 // it was added even when the tr ansaction fails. | |
| 368 if err := ds.Put(ds.WithTransact ion(c, nil), noTxnPM); err != nil { | |
| 369 return err | |
| 370 } | |
| 371 return testError | |
| 372 }, nil) | |
| 373 So(err, ShouldEqual, testError) | |
| 374 | |
| 375 // Confirm that noTxnPM was added. | |
| 376 So(ds.CurrentTransaction(c), ShouldBeNil ) | |
| 377 So(ds.Get(c, noTxnPM), ShouldBeNil) | |
| 378 }) | |
| 379 | |
| 346 Convey("Concurrent transactions only accept one set of changes", func() { | 380 Convey("Concurrent transactions only accept one set of changes", func() { |
| 347 // Note: I think this implementation is actually /slightly/ wrong. | 381 // Note: I think this implementation is actually /slightly/ wrong. |
| 348 // According to my read of the docs for appengine, when you open a | 382 // According to my read of the docs for appengine, when you open a |
| 349 // transaction it actually (essentially) holds a reference to the | 383 // transaction it actually (essentially) holds a reference to the |
| 350 // entire datastore. Our implementation takes a snapshot of the | 384 // entire datastore. Our implementation takes a snapshot of the |
| 351 // entity group as soon as something obs erves/affects it. | 385 // entity group as soon as something obs erves/affects it. |
| 352 // | 386 // |
| 353 // That said... I'm not sure if there's really a semantic difference. | 387 // That said... I'm not sure if there's really a semantic difference. |
| 354 » » » » » err := ds.RunInTransaction(func(c contex t.Context) error { | 388 » » » » » err := ds.RunInTransaction(c, func(c con text.Context) error { |
| 355 » » » » » » So(dsS.Get(c).Put(&Foo{ID: 1, Va l: 21}), ShouldBeNil) | 389 » » » » » » So(ds.Put(c, &Foo{ID: 1, Val: 21 }), ShouldBeNil) |
| 356 | 390 |
| 357 » » » » » » err := dsS.GetNoTxn(c).RunInTran saction(func(c context.Context) error { | 391 » » » » » » err := ds.RunInTransaction(ds.Wi thTransaction(c, nil), func(c context.Context) error { |
| 358 » » » » » » » So(dsS.Get(c).Put(&Foo{I D: 1, Val: 27}), ShouldBeNil) | 392 » » » » » » » So(ds.Put(c, &Foo{ID: 1, Val: 27}), ShouldBeNil) |
| 359 return nil | 393 return nil |
| 360 }, nil) | 394 }, nil) |
| 361 So(err, ShouldBeNil) | 395 So(err, ShouldBeNil) |
| 362 | 396 |
| 363 return nil | 397 return nil |
| 364 }, nil) | 398 }, nil) |
| 365 So(err.Error(), ShouldContainSubstring, "concurrent") | 399 So(err.Error(), ShouldContainSubstring, "concurrent") |
| 366 | 400 |
| 367 f := &Foo{ID: 1} | 401 f := &Foo{ID: 1} |
| 368 » » » » » So(ds.Get(f), ShouldBeNil) | 402 » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 369 So(f.Val, ShouldEqual, 27) | 403 So(f.Val, ShouldEqual, 27) |
| 370 }) | 404 }) |
| 371 | 405 |
| 372 Convey("XG", func() { | 406 Convey("XG", func() { |
| 373 Convey("Modifying two groups with XG=fal se is invalid", func() { | 407 Convey("Modifying two groups with XG=fal se is invalid", func() { |
| 374 » » » » » » err := ds.RunInTransaction(func( c context.Context) error { | 408 » » » » » » err := ds.RunInTransaction(c, fu nc(c context.Context) error { |
| 375 » » » » » » » ds := dsS.Get(c) | |
| 376 f := &Foo{ID: 1, Val: 20 0} | 409 f := &Foo{ID: 1, Val: 20 0} |
| 377 » » » » » » » So(ds.Put(f), ShouldBeNi l) | 410 » » » » » » » So(ds.Put(c, f), ShouldB eNil) |
| 378 | 411 |
| 379 f.ID = 2 | 412 f.ID = 2 |
| 380 » » » » » » » err := ds.Put(f) | 413 » » » » » » » err := ds.Put(c, f) |
| 381 So(err.Error(), ShouldCo ntainSubstring, "cross-group") | 414 So(err.Error(), ShouldCo ntainSubstring, "cross-group") |
| 382 return err | 415 return err |
| 383 }, nil) | 416 }, nil) |
| 384 So(err.Error(), ShouldContainSub string, "cross-group") | 417 So(err.Error(), ShouldContainSub string, "cross-group") |
| 385 }) | 418 }) |
| 386 | 419 |
| 387 Convey("Modifying >25 groups with XG=tru e is invald", func() { | 420 Convey("Modifying >25 groups with XG=tru e is invald", func() { |
| 388 » » » » » » err := ds.RunInTransaction(func( c context.Context) error { | 421 » » » » » » err := ds.RunInTransaction(c, fu nc(c context.Context) error { |
| 389 » » » » » » » ds := dsS.Get(c) | |
| 390 foos := make([]Foo, 25) | 422 foos := make([]Foo, 25) |
| 391 for i := int64(1); i < 2 6; i++ { | 423 for i := int64(1); i < 2 6; i++ { |
| 392 foos[i-1].ID = i | 424 foos[i-1].ID = i |
| 393 foos[i-1].Val = 200 | 425 foos[i-1].Val = 200 |
| 394 } | 426 } |
| 395 » » » » » » » So(ds.PutMulti(foos), Sh ouldBeNil) | 427 » » » » » » » So(ds.Put(c, foos), Shou ldBeNil) |
| 396 » » » » » » » err := ds.Put(&Foo{ID: 2 6}) | 428 » » » » » » » err := ds.Put(c, &Foo{ID : 26}) |
| 397 So(err.Error(), ShouldCo ntainSubstring, "too many entity groups") | 429 So(err.Error(), ShouldCo ntainSubstring, "too many entity groups") |
| 398 return err | 430 return err |
| 399 » » » » » » }, &dsS.TransactionOptions{XG: t rue}) | 431 » » » » » » }, &ds.TransactionOptions{XG: tr ue}) |
| 400 So(err.Error(), ShouldContainSub string, "too many entity groups") | 432 So(err.Error(), ShouldContainSub string, "too many entity groups") |
| 401 }) | 433 }) |
| 402 }) | 434 }) |
| 403 | 435 |
| 404 Convey("Errors and panics", func() { | 436 Convey("Errors and panics", func() { |
| 405 Convey("returning an error aborts", func () { | 437 Convey("returning an error aborts", func () { |
| 406 » » » » » » err := ds.RunInTransaction(func( c context.Context) error { | 438 » » » » » » err := ds.RunInTransaction(c, fu nc(c context.Context) error { |
| 407 » » » » » » » ds := dsS.Get(c) | 439 » » » » » » » So(ds.Put(c, &Foo{ID: 1, Val: 200}), ShouldBeNil) |
| 408 » » » » » » » So(ds.Put(&Foo{ID: 1, Va l: 200}), ShouldBeNil) | |
| 409 return fmt.Errorf("thing y") | 440 return fmt.Errorf("thing y") |
| 410 }, nil) | 441 }, nil) |
| 411 So(err.Error(), ShouldEqual, "th ingy") | 442 So(err.Error(), ShouldEqual, "th ingy") |
| 412 | 443 |
| 413 f := &Foo{ID: 1} | 444 f := &Foo{ID: 1} |
| 414 » » » » » » So(ds.Get(f), ShouldBeNil) | 445 » » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 415 So(f.Val, ShouldEqual, 10) | 446 So(f.Val, ShouldEqual, 10) |
| 416 }) | 447 }) |
| 417 | 448 |
| 418 Convey("panicing aborts", func() { | 449 Convey("panicing aborts", func() { |
| 419 So(func() { | 450 So(func() { |
| 420 » » » » » » » So(ds.RunInTransaction(f unc(c context.Context) error { | 451 » » » » » » » So(ds.RunInTransaction(c , func(c context.Context) error { |
| 421 » » » » » » » » ds := dsS.Get(c) | 452 » » » » » » » » So(ds.Put(c, &Fo o{Val: 200}), ShouldBeNil) |
| 422 » » » » » » » » So(ds.Put(&Foo{V al: 200}), ShouldBeNil) | |
| 423 panic("wheeeeee" ) | 453 panic("wheeeeee" ) |
| 424 }, nil), ShouldBeNil) | 454 }, nil), ShouldBeNil) |
| 425 }, ShouldPanic) | 455 }, ShouldPanic) |
| 426 | 456 |
| 427 f := &Foo{ID: 1} | 457 f := &Foo{ID: 1} |
| 428 » » » » » » So(ds.Get(f), ShouldBeNil) | 458 » » » » » » So(ds.Get(c, f), ShouldBeNil) |
| 429 So(f.Val, ShouldEqual, 10) | 459 So(f.Val, ShouldEqual, 10) |
| 430 }) | 460 }) |
| 431 }) | 461 }) |
| 432 | 462 |
| 433 Convey("Transaction retries", func() { | 463 Convey("Transaction retries", func() { |
| 434 » » » » » tst := ds.Testable() | 464 » » » » » tst := ds.GetTestable(c) |
| 435 Reset(func() { tst.SetTransactionRetryCo unt(0) }) | 465 Reset(func() { tst.SetTransactionRetryCo unt(0) }) |
| 436 | 466 |
| 437 Convey("SetTransactionRetryCount set to zero", func() { | 467 Convey("SetTransactionRetryCount set to zero", func() { |
| 438 tst.SetTransactionRetryCount(0) | 468 tst.SetTransactionRetryCount(0) |
| 439 calls := 0 | 469 calls := 0 |
| 440 » » » » » » So(ds.RunInTransaction(func(c co ntext.Context) error { | 470 » » » » » » So(ds.RunInTransaction(c, func(c context.Context) error { |
| 441 calls++ | 471 calls++ |
| 442 return nil | 472 return nil |
| 443 }, nil), ShouldBeNil) | 473 }, nil), ShouldBeNil) |
| 444 So(calls, ShouldEqual, 1) | 474 So(calls, ShouldEqual, 1) |
| 445 }) | 475 }) |
| 446 | 476 |
| 447 Convey("default TransactionOptions is 3 attempts", func() { | 477 Convey("default TransactionOptions is 3 attempts", func() { |
| 448 tst.SetTransactionRetryCount(100 ) // more than 3 | 478 tst.SetTransactionRetryCount(100 ) // more than 3 |
| 449 calls := 0 | 479 calls := 0 |
| 450 » » » » » » So(ds.RunInTransaction(func(c co ntext.Context) error { | 480 » » » » » » So(ds.RunInTransaction(c, func(c context.Context) error { |
| 451 calls++ | 481 calls++ |
| 452 return nil | 482 return nil |
| 453 » » » » » » }, nil), ShouldEqual, dsS.ErrCon currentTransaction) | 483 » » » » » » }, nil), ShouldEqual, ds.ErrConc urrentTransaction) |
| 454 So(calls, ShouldEqual, 3) | 484 So(calls, ShouldEqual, 3) |
| 455 }) | 485 }) |
| 456 | 486 |
| 457 Convey("non-default TransactionOptions " , func() { | 487 Convey("non-default TransactionOptions " , func() { |
| 458 tst.SetTransactionRetryCount(100 ) // more than 20 | 488 tst.SetTransactionRetryCount(100 ) // more than 20 |
| 459 calls := 0 | 489 calls := 0 |
| 460 » » » » » » So(ds.RunInTransaction(func(c co ntext.Context) error { | 490 » » » » » » So(ds.RunInTransaction(c, func(c context.Context) error { |
| 461 calls++ | 491 calls++ |
| 462 return nil | 492 return nil |
| 463 » » » » » » }, &dsS.TransactionOptions{Attem pts: 20}), ShouldEqual, dsS.ErrConcurrentTransaction) | 493 » » » » » » }, &ds.TransactionOptions{Attemp ts: 20}), ShouldEqual, ds.ErrConcurrentTransaction) |
| 464 So(calls, ShouldEqual, 20) | 494 So(calls, ShouldEqual, 20) |
| 465 }) | 495 }) |
| 466 | 496 |
| 467 Convey("SetTransactionRetryCount is resp ected", func() { | 497 Convey("SetTransactionRetryCount is resp ected", func() { |
| 468 tst.SetTransactionRetryCount(1) // less than 3 | 498 tst.SetTransactionRetryCount(1) // less than 3 |
| 469 calls := 0 | 499 calls := 0 |
| 470 » » » » » » So(ds.RunInTransaction(func(c co ntext.Context) error { | 500 » » » » » » So(ds.RunInTransaction(c, func(c context.Context) error { |
| 471 calls++ | 501 calls++ |
| 472 return nil | 502 return nil |
| 473 }, nil), ShouldBeNil) | 503 }, nil), ShouldBeNil) |
| 474 So(calls, ShouldEqual, 2) | 504 So(calls, ShouldEqual, 2) |
| 475 }) | 505 }) |
| 476 | 506 |
| 477 Convey("fatal errors are not retried", f unc() { | 507 Convey("fatal errors are not retried", f unc() { |
| 478 tst.SetTransactionRetryCount(1) | 508 tst.SetTransactionRetryCount(1) |
| 479 calls := 0 | 509 calls := 0 |
| 480 » » » » » » So(ds.RunInTransaction(func(c co ntext.Context) error { | 510 » » » » » » So(ds.RunInTransaction(c, func(c context.Context) error { |
| 481 calls++ | 511 calls++ |
| 482 return fmt.Errorf("omg") | 512 return fmt.Errorf("omg") |
| 483 }, nil).Error(), ShouldEqual, "o mg") | 513 }, nil).Error(), ShouldEqual, "o mg") |
| 484 So(calls, ShouldEqual, 1) | 514 So(calls, ShouldEqual, 1) |
| 485 }) | 515 }) |
| 486 }) | 516 }) |
| 487 }) | 517 }) |
| 488 }) | 518 }) |
| 489 | 519 |
| 490 Convey("Testable.Consistent", func() { | 520 Convey("Testable.Consistent", func() { |
| 491 Convey("false", func() { | 521 Convey("false", func() { |
| 492 » » » » ds.Testable().Consistent(false) // the default | 522 » » » » ds.GetTestable(c).Consistent(false) // the defau lt |
| 493 for i := 0; i < 10; i++ { | 523 for i := 0; i < 10; i++ { |
| 494 » » » » » So(ds.Put(&Foo{ID: int64(i + 1), Val: i + 1}), ShouldBeNil) | 524 » » » » » So(ds.Put(c, &Foo{ID: int64(i + 1), Val: i + 1}), ShouldBeNil) |
| 495 } | 525 } |
| 496 » » » » q := dsS.NewQuery("Foo").Gt("Val", 3) | 526 » » » » q := ds.NewQuery("Foo").Gt("Val", 3) |
| 497 » » » » count, err := ds.Count(q) | 527 » » » » count, err := ds.Count(c, q) |
| 498 So(err, ShouldBeNil) | 528 So(err, ShouldBeNil) |
| 499 So(count, ShouldEqual, 0) | 529 So(count, ShouldEqual, 0) |
| 500 | 530 |
| 501 » » » » So(ds.Delete(ds.MakeKey("Foo", 4)), ShouldBeNil) | 531 » » » » So(ds.Delete(c, ds.MakeKey(c, "Foo", 4)), Should BeNil) |
| 502 | 532 |
| 503 » » » » count, err = ds.Count(q) | 533 » » » » count, err = ds.Count(c, q) |
| 504 So(err, ShouldBeNil) | 534 So(err, ShouldBeNil) |
| 505 So(count, ShouldEqual, 0) | 535 So(count, ShouldEqual, 0) |
| 506 | 536 |
| 507 » » » » ds.Testable().Consistent(true) | 537 » » » » ds.GetTestable(c).Consistent(true) |
| 508 » » » » count, err = ds.Count(q) | 538 » » » » count, err = ds.Count(c, q) |
| 509 So(err, ShouldBeNil) | 539 So(err, ShouldBeNil) |
| 510 So(count, ShouldEqual, 6) | 540 So(count, ShouldEqual, 6) |
| 511 }) | 541 }) |
| 512 | 542 |
| 513 Convey("true", func() { | 543 Convey("true", func() { |
| 514 » » » » ds.Testable().Consistent(true) | 544 » » » » ds.GetTestable(c).Consistent(true) |
| 515 for i := 0; i < 10; i++ { | 545 for i := 0; i < 10; i++ { |
| 516 » » » » » So(ds.Put(&Foo{ID: int64(i + 1), Val: i + 1}), ShouldBeNil) | 546 » » » » » So(ds.Put(c, &Foo{ID: int64(i + 1), Val: i + 1}), ShouldBeNil) |
| 517 } | 547 } |
| 518 » » » » q := dsS.NewQuery("Foo").Gt("Val", 3) | 548 » » » » q := ds.NewQuery("Foo").Gt("Val", 3) |
| 519 » » » » count, err := ds.Count(q) | 549 » » » » count, err := ds.Count(c, q) |
| 520 So(err, ShouldBeNil) | 550 So(err, ShouldBeNil) |
| 521 So(count, ShouldEqual, 7) | 551 So(count, ShouldEqual, 7) |
| 522 | 552 |
| 523 » » » » So(ds.Delete(ds.MakeKey("Foo", 4)), ShouldBeNil) | 553 » » » » So(ds.Delete(c, ds.MakeKey(c, "Foo", 4)), Should BeNil) |
| 524 | 554 |
| 525 » » » » count, err = ds.Count(q) | 555 » » » » count, err = ds.Count(c, q) |
| 526 So(err, ShouldBeNil) | 556 So(err, ShouldBeNil) |
| 527 So(count, ShouldEqual, 6) | 557 So(count, ShouldEqual, 6) |
| 528 }) | 558 }) |
| 529 }) | 559 }) |
| 530 | 560 |
| 531 Convey("Testable.DisableSpecialEntities", func() { | 561 Convey("Testable.DisableSpecialEntities", func() { |
| 532 » » » ds.Testable().DisableSpecialEntities(true) | 562 » » » ds.GetTestable(c).DisableSpecialEntities(true) |
| 533 | 563 |
| 534 » » » So(ds.Put(&Foo{}), ShouldErrLike, "allocateIDs is disabl ed") | 564 » » » So(ds.Put(c, &Foo{}), ShouldErrLike, "allocateIDs is dis abled") |
| 535 | 565 |
| 536 » » » So(ds.Put(&Foo{ID: 1}), ShouldBeNil) | 566 » » » So(ds.Put(c, &Foo{ID: 1}), ShouldBeNil) |
| 537 | 567 |
| 538 » » » ds.Testable().CatchupIndexes() | 568 » » » ds.GetTestable(c).CatchupIndexes() |
| 539 | 569 |
| 540 » » » count, err := ds.Count(dsS.NewQuery("")) | 570 » » » count, err := ds.Count(c, ds.NewQuery("")) |
| 541 So(err, ShouldBeNil) | 571 So(err, ShouldBeNil) |
| 542 So(count, ShouldEqual, 1) // normally this would include __entity_group__ | 572 So(count, ShouldEqual, 1) // normally this would include __entity_group__ |
| 543 }) | 573 }) |
| 544 | 574 |
| 545 Convey("Datastore namespace interaction", func() { | 575 Convey("Datastore namespace interaction", func() { |
| 546 run := func(rc context.Context, txn bool) (putErr, getEr r, queryErr, countErr error) { | 576 run := func(rc context.Context, txn bool) (putErr, getEr r, queryErr, countErr error) { |
| 547 var foo Foo | 577 var foo Foo |
| 548 | 578 |
| 549 putFunc := func(doC context.Context) error { | 579 putFunc := func(doC context.Context) error { |
| 550 » » » » » return dsS.Get(doC).Put(&foo) | 580 » » » » » return ds.Put(doC, &foo) |
| 551 } | 581 } |
| 552 | 582 |
| 553 doFunc := func(doC context.Context) { | 583 doFunc := func(doC context.Context) { |
| 554 » » » » » ds := dsS.Get(doC) | 584 » » » » » getErr = ds.Get(doC, &foo) |
| 555 » » » » » getErr = ds.Get(&foo) | |
| 556 | 585 |
| 557 » » » » » q := dsS.NewQuery("Foo").Ancestor(ds.Key ForObj(&foo)) | 586 » » » » » q := ds.NewQuery("Foo").Ancestor(ds.KeyF orObj(doC, &foo)) |
| 558 » » » » » queryErr = ds.Run(q, func(f *Foo) error { return nil }) | 587 » » » » » queryErr = ds.Run(doC, q, func(f *Foo) e rror { return nil }) |
| 559 » » » » » _, countErr = ds.Count(q) | 588 » » » » » _, countErr = ds.Count(doC, q) |
| 560 } | 589 } |
| 561 | 590 |
| 562 if txn { | 591 if txn { |
| 563 » » » » » putErr = dsS.Get(rc).RunInTransaction(fu nc(ic context.Context) error { | 592 » » » » » putErr = ds.RunInTransaction(rc, func(ic context.Context) error { |
| 564 return putFunc(ic) | 593 return putFunc(ic) |
| 565 }, nil) | 594 }, nil) |
| 566 if putErr != nil { | 595 if putErr != nil { |
| 567 return | 596 return |
| 568 } | 597 } |
| 569 | 598 |
| 570 » » » » » dsS.Get(rc).Testable().CatchupIndexes() | 599 » » » » » ds.GetTestable(rc).CatchupIndexes() |
| 571 » » » » » dsS.Get(rc).RunInTransaction(func(ic con text.Context) error { | 600 » » » » » ds.RunInTransaction(rc, func(ic context. Context) error { |
| 572 doFunc(ic) | 601 doFunc(ic) |
| 573 return nil | 602 return nil |
| 574 }, nil) | 603 }, nil) |
| 575 } else { | 604 } else { |
| 576 putErr = putFunc(rc) | 605 putErr = putFunc(rc) |
| 577 if putErr != nil { | 606 if putErr != nil { |
| 578 return | 607 return |
| 579 } | 608 } |
| 580 » » » » » dsS.Get(rc).Testable().CatchupIndexes() | 609 » » » » » ds.GetTestable(rc).CatchupIndexes() |
| 581 doFunc(rc) | 610 doFunc(rc) |
| 582 } | 611 } |
| 583 return | 612 return |
| 584 } | 613 } |
| 585 | 614 |
| 586 for _, txn := range []bool{false, true} { | 615 for _, txn := range []bool{false, true} { |
| 587 Convey(fmt.Sprintf("In transaction? %v", txn), f unc() { | 616 Convey(fmt.Sprintf("In transaction? %v", txn), f unc() { |
| 588 Convey("With no namespace installed, can Put, Get, Query, and Count.", func() { | 617 Convey("With no namespace installed, can Put, Get, Query, and Count.", func() { |
| 589 » » » » » » _, has := infoS.Get(c).GetNamesp ace() | 618 » » » » » » So(infoS.GetNamespace(c), Should Equal, "") |
| 590 » » » » » » So(has, ShouldBeFalse) | |
| 591 | 619 |
| 592 putErr, getErr, queryErr, countE rr := run(c, txn) | 620 putErr, getErr, queryErr, countE rr := run(c, txn) |
| 593 So(putErr, ShouldBeNil) | 621 So(putErr, ShouldBeNil) |
| 594 So(getErr, ShouldBeNil) | 622 So(getErr, ShouldBeNil) |
| 595 So(queryErr, ShouldBeNil) | 623 So(queryErr, ShouldBeNil) |
| 596 So(countErr, ShouldBeNil) | 624 So(countErr, ShouldBeNil) |
| 597 }) | 625 }) |
| 598 | 626 |
| 599 Convey("With a namespace installed, can Put, Get, Query, and Count.", func() { | 627 Convey("With a namespace installed, can Put, Get, Query, and Count.", func() { |
| 600 » » » » » » putErr, getErr, queryErr, countE rr := run(infoS.Get(c).MustNamespace("foo"), txn) | 628 » » » » » » putErr, getErr, queryErr, countE rr := run(infoS.MustNamespace(c, "foo"), txn) |
| 601 So(putErr, ShouldBeNil) | 629 So(putErr, ShouldBeNil) |
| 602 So(getErr, ShouldBeNil) | 630 So(getErr, ShouldBeNil) |
| 603 So(queryErr, ShouldBeNil) | 631 So(queryErr, ShouldBeNil) |
| 604 So(countErr, ShouldBeNil) | 632 So(countErr, ShouldBeNil) |
| 605 }) | 633 }) |
| 606 }) | 634 }) |
| 607 } | 635 } |
| 608 }) | 636 }) |
| 609 }) | 637 }) |
| 610 } | 638 } |
| 611 | 639 |
| 612 func TestCompoundIndexes(t *testing.T) { | 640 func TestCompoundIndexes(t *testing.T) { |
| 613 t.Parallel() | 641 t.Parallel() |
| 614 | 642 |
| 615 » idxKey := func(def dsS.IndexDefinition) string { | 643 » idxKey := func(def ds.IndexDefinition) string { |
| 616 So(def, ShouldNotBeNil) | 644 So(def, ShouldNotBeNil) |
| 617 return "idx::" + string(serialize.ToBytes(*def.PrepForIdxTable() )) | 645 return "idx::" + string(serialize.ToBytes(*def.PrepForIdxTable() )) |
| 618 } | 646 } |
| 619 | 647 |
| 620 numItms := func(c memCollection) uint64 { | 648 numItms := func(c memCollection) uint64 { |
| 621 ret, _ := c.GetTotals() | 649 ret, _ := c.GetTotals() |
| 622 return ret | 650 return ret |
| 623 } | 651 } |
| 624 | 652 |
| 625 Convey("Test Compound indexes", t, func() { | 653 Convey("Test Compound indexes", t, func() { |
| 626 type Model struct { | 654 type Model struct { |
| 627 ID int64 `gae:"$id"` | 655 ID int64 `gae:"$id"` |
| 628 | 656 |
| 629 Field1 []string | 657 Field1 []string |
| 630 Field2 []int64 | 658 Field2 []int64 |
| 631 } | 659 } |
| 632 | 660 |
| 633 c := Use(context.Background()) | 661 c := Use(context.Background()) |
| 634 » » ds := dsS.Get(c) | 662 » » t := ds.GetTestable(c).(*dsImpl) |
| 635 » » t := ds.Testable().(*dsImpl) | |
| 636 head := t.data.head | 663 head := t.data.head |
| 637 | 664 |
| 638 » » So(ds.Put(&Model{1, []string{"hello", "world"}, []int64{10, 11}} ), ShouldBeNil) | 665 » » So(ds.Put(c, &Model{1, []string{"hello", "world"}, []int64{10, 1 1}}), ShouldBeNil) |
| 639 | 666 |
| 640 » » idx := dsS.IndexDefinition{ | 667 » » idx := ds.IndexDefinition{ |
| 641 Kind: "Model", | 668 Kind: "Model", |
| 642 » » » SortBy: []dsS.IndexColumn{ | 669 » » » SortBy: []ds.IndexColumn{ |
| 643 {Property: "Field2"}, | 670 {Property: "Field2"}, |
| 644 }, | 671 }, |
| 645 } | 672 } |
| 646 | 673 |
| 647 coll := head.GetCollection(idxKey(idx)) | 674 coll := head.GetCollection(idxKey(idx)) |
| 648 So(coll, ShouldNotBeNil) | 675 So(coll, ShouldNotBeNil) |
| 649 So(numItms(coll), ShouldEqual, 2) | 676 So(numItms(coll), ShouldEqual, 2) |
| 650 | 677 |
| 651 idx.SortBy[0].Property = "Field1" | 678 idx.SortBy[0].Property = "Field1" |
| 652 coll = head.GetCollection(idxKey(idx)) | 679 coll = head.GetCollection(idxKey(idx)) |
| 653 So(coll, ShouldNotBeNil) | 680 So(coll, ShouldNotBeNil) |
| 654 So(numItms(coll), ShouldEqual, 2) | 681 So(numItms(coll), ShouldEqual, 2) |
| 655 | 682 |
| 656 » » idx.SortBy = append(idx.SortBy, dsS.IndexColumn{Property: "Field 1"}) | 683 » » idx.SortBy = append(idx.SortBy, ds.IndexColumn{Property: "Field1 "}) |
| 657 So(head.GetCollection(idxKey(idx)), ShouldBeNil) | 684 So(head.GetCollection(idxKey(idx)), ShouldBeNil) |
| 658 | 685 |
| 659 t.AddIndexes(&idx) | 686 t.AddIndexes(&idx) |
| 660 coll = head.GetCollection(idxKey(idx)) | 687 coll = head.GetCollection(idxKey(idx)) |
| 661 So(coll, ShouldNotBeNil) | 688 So(coll, ShouldNotBeNil) |
| 662 So(numItms(coll), ShouldEqual, 4) | 689 So(numItms(coll), ShouldEqual, 4) |
| 663 }) | 690 }) |
| 664 } | 691 } |
| 665 | 692 |
| 666 // High level test for regression in how zero time is stored, | 693 // High level test for regression in how zero time is stored, |
| 667 // see https://codereview.chromium.org/1334043003/ | 694 // see https://codereview.chromium.org/1334043003/ |
| 668 func TestDefaultTimeField(t *testing.T) { | 695 func TestDefaultTimeField(t *testing.T) { |
| 669 t.Parallel() | 696 t.Parallel() |
| 670 | 697 |
| 671 Convey("Default time.Time{} can be stored", t, func() { | 698 Convey("Default time.Time{} can be stored", t, func() { |
| 672 type Model struct { | 699 type Model struct { |
| 673 ID int64 `gae:"$id"` | 700 ID int64 `gae:"$id"` |
| 674 Time time.Time | 701 Time time.Time |
| 675 } | 702 } |
| 676 » » ds := dsS.Get(Use(context.Background())) | 703 » » c := Use(context.Background()) |
| 677 m := Model{ID: 1} | 704 m := Model{ID: 1} |
| 678 » » So(ds.Put(&m), ShouldBeNil) | 705 » » So(ds.Put(c, &m), ShouldBeNil) |
| 679 | 706 |
| 680 // Reset to something non zero to ensure zero is fetched. | 707 // Reset to something non zero to ensure zero is fetched. |
| 681 m.Time = time.Now().UTC() | 708 m.Time = time.Now().UTC() |
| 682 » » So(ds.Get(&m), ShouldBeNil) | 709 » » So(ds.Get(c, &m), ShouldBeNil) |
| 683 So(m.Time.IsZero(), ShouldBeTrue) | 710 So(m.Time.IsZero(), ShouldBeTrue) |
| 684 }) | 711 }) |
| 685 } | 712 } |
| 686 | 713 |
| 687 func TestNewDatastore(t *testing.T) { | 714 func TestNewDatastore(t *testing.T) { |
| 688 t.Parallel() | 715 t.Parallel() |
| 689 | 716 |
| 690 Convey("Can get and use a NewDatastore", t, func() { | 717 Convey("Can get and use a NewDatastore", t, func() { |
| 691 c := UseWithAppID(context.Background(), "dev~aid") | 718 c := UseWithAppID(context.Background(), "dev~aid") |
| 692 » » c = infoS.Get(c).MustNamespace("ns") | 719 » » c = infoS.MustNamespace(c, "ns") |
| 693 » » ds := NewDatastore(infoS.Get(c)) | |
| 694 | 720 |
| 695 » » k := ds.MakeKey("Something", 1) | 721 » » dsInst := NewDatastore(c, infoS.Raw(c)) |
| 722 » » c = ds.SetRaw(c, dsInst) | |
| 723 | |
| 724 » » k := ds.MakeKey(c, "Something", 1) | |
| 696 So(k.AppID(), ShouldEqual, "dev~aid") | 725 So(k.AppID(), ShouldEqual, "dev~aid") |
| 697 So(k.Namespace(), ShouldEqual, "ns") | 726 So(k.Namespace(), ShouldEqual, "ns") |
| 698 | 727 |
| 699 type Model struct { | 728 type Model struct { |
| 700 ID int64 `gae:"$id"` | 729 ID int64 `gae:"$id"` |
| 701 Value []int64 | 730 Value []int64 |
| 702 } | 731 } |
| 703 » » So(ds.Put(&Model{ID: 1, Value: []int64{20, 30}}), ShouldBeNil) | 732 » » So(ds.Put(c, &Model{ID: 1, Value: []int64{20, 30}}), ShouldBeNil ) |
| 704 | 733 |
| 705 » » vals := []dsS.PropertyMap{} | 734 » » vals := []ds.PropertyMap{} |
| 706 » » So(ds.GetAll(dsS.NewQuery("Model").Project("Value"), &vals), Sho uldBeNil) | 735 » » So(ds.GetAll(c, ds.NewQuery("Model").Project("Value"), &vals), S houldBeNil) |
| 707 So(len(vals), ShouldEqual, 2) | 736 So(len(vals), ShouldEqual, 2) |
| 708 | 737 |
| 709 So(vals[0]["Value"][0].Value(), ShouldEqual, 20) | 738 So(vals[0]["Value"][0].Value(), ShouldEqual, 20) |
| 710 So(vals[1]["Value"][0].Value(), ShouldEqual, 30) | 739 So(vals[1]["Value"][0].Value(), ShouldEqual, 30) |
| 711 }) | 740 }) |
| 712 } | 741 } |
| 713 | 742 |
| 714 func TestAddIndexes(t *testing.T) { | 743 func TestAddIndexes(t *testing.T) { |
| 715 t.Parallel() | 744 t.Parallel() |
| 716 | 745 |
| 717 Convey("Test Testable.AddIndexes", t, func() { | 746 Convey("Test Testable.AddIndexes", t, func() { |
| 718 ctx := UseWithAppID(context.Background(), "aid") | 747 ctx := UseWithAppID(context.Background(), "aid") |
| 719 namespaces := []string{"", "good", "news", "everyone"} | 748 namespaces := []string{"", "good", "news", "everyone"} |
| 720 | 749 |
| 721 Convey("After adding datastore entries, can query against indexe s in various namespaces", func() { | 750 Convey("After adding datastore entries, can query against indexe s in various namespaces", func() { |
| 722 foos := []*Foo{ | 751 foos := []*Foo{ |
| 723 {ID: 1, Val: 1, Name: "foo"}, | 752 {ID: 1, Val: 1, Name: "foo"}, |
| 724 {ID: 2, Val: 2, Name: "bar"}, | 753 {ID: 2, Val: 2, Name: "bar"}, |
| 725 {ID: 3, Val: 2, Name: "baz"}, | 754 {ID: 3, Val: 2, Name: "baz"}, |
| 726 } | 755 } |
| 727 for _, ns := range namespaces { | 756 for _, ns := range namespaces { |
| 728 » » » » So(dsS.Get(infoS.Get(ctx).MustNamespace(ns)).Put Multi(foos), ShouldBeNil) | 757 » » » » So(ds.Put(infoS.MustNamespace(ctx, ns), foos), S houldBeNil) |
| 729 } | 758 } |
| 730 | 759 |
| 731 // Initial query, no indexes, will fail. | 760 // Initial query, no indexes, will fail. |
| 732 » » » dsS.Get(ctx).Testable().CatchupIndexes() | 761 » » » ds.GetTestable(ctx).CatchupIndexes() |
| 733 | 762 |
| 734 var results []*Foo | 763 var results []*Foo |
| 735 » » » q := dsS.NewQuery("Foo").Eq("Val", 2).Gte("Name", "bar") | 764 » » » q := ds.NewQuery("Foo").Eq("Val", 2).Gte("Name", "bar") |
| 736 » » » So(dsS.Get(ctx).GetAll(q, &results), ShouldErrLike, "Ins ufficient indexes") | 765 » » » So(ds.GetAll(ctx, q, &results), ShouldErrLike, "Insuffic ient indexes") |
| 737 | 766 |
| 738 // Add index for default namespace. | 767 // Add index for default namespace. |
| 739 » » » dsS.Get(ctx).Testable().AddIndexes(&dsS.IndexDefinition{ | 768 » » » ds.GetTestable(ctx).AddIndexes(&ds.IndexDefinition{ |
| 740 Kind: "Foo", | 769 Kind: "Foo", |
| 741 » » » » SortBy: []dsS.IndexColumn{ | 770 » » » » SortBy: []ds.IndexColumn{ |
| 742 {Property: "Val"}, | 771 {Property: "Val"}, |
| 743 {Property: "Name"}, | 772 {Property: "Name"}, |
| 744 }, | 773 }, |
| 745 }) | 774 }) |
| 746 » » » dsS.Get(ctx).Testable().CatchupIndexes() | 775 » » » ds.GetTestable(ctx).CatchupIndexes() |
| 747 | 776 |
| 748 for _, ns := range namespaces { | 777 for _, ns := range namespaces { |
| 749 if ns == "" { | 778 if ns == "" { |
| 750 // Skip query test for empty namespace, as this is invalid. | 779 // Skip query test for empty namespace, as this is invalid. |
| 751 continue | 780 continue |
| 752 } | 781 } |
| 753 | 782 |
| 754 results = nil | 783 results = nil |
| 755 » » » » So(dsS.Get(infoS.Get(ctx).MustNamespace(ns)).Get All(q, &results), ShouldBeNil) | 784 » » » » So(ds.GetAll(infoS.MustNamespace(ctx, ns), q, &r esults), ShouldBeNil) |
| 756 So(len(results), ShouldEqual, 2) | 785 So(len(results), ShouldEqual, 2) |
| 757 } | 786 } |
| 758 | 787 |
| 759 // Add "foos" to a new namespace, then confirm that it g ets indexed. | 788 // Add "foos" to a new namespace, then confirm that it g ets indexed. |
| 760 » » » So(dsS.Get(infoS.Get(ctx).MustNamespace("qux")).PutMulti (foos), ShouldBeNil) | 789 » » » So(ds.Put(infoS.MustNamespace(ctx, "qux"), foos), Should BeNil) |
| 761 » » » dsS.Get(ctx).Testable().CatchupIndexes() | 790 » » » ds.GetTestable(ctx).CatchupIndexes() |
| 762 | 791 |
| 763 results = nil | 792 results = nil |
| 764 » » » So(dsS.Get(infoS.Get(ctx).MustNamespace("qux")).GetAll(q , &results), ShouldBeNil) | 793 » » » So(ds.GetAll(infoS.MustNamespace(ctx, "qux"), q, &result s), ShouldBeNil) |
| 765 So(len(results), ShouldEqual, 2) | 794 So(len(results), ShouldEqual, 2) |
| 766 }) | 795 }) |
| 767 }) | 796 }) |
| 768 } | 797 } |
| OLD | NEW |