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