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