func TestMemcacheNamespace(t *testing.T) { c, closeFunc := NewContext(t) defer closeFunc() type testEntity struct { IntVal int } // Illegal namespace chars. nds.SetMemcacheNamespace("£££") key := datastore.NewKey(c, "Entity", "", 1, nil) if err := nds.Get(c, key, &testEntity{}); err == nil { t.Fatal("expected namespace error") } if _, err := nds.Put(c, key, &testEntity{}); err == nil { t.Fatal("expected namespace error") } if err := nds.Delete(c, key); err == nil { t.Fatal("expected namespace error") } if err := nds.RunInTransaction(c, func(tc context.Context) error { return nil }, nil); err == nil { t.Fatal("expected namespace error") } nds.SetMemcacheNamespace("") }
func TestDeleteInTransaction(t *testing.T) { c, closeFunc := NewContext(t) defer closeFunc() type testEntity struct { Val int } key := datastore.NewKey(c, "TestEntity", "", 1, nil) if _, err := nds.Put(c, key, &testEntity{2}); err != nil { t.Fatal(err) } // Prime cache. if err := nds.Get(c, key, &testEntity{}); err != nil { t.Fatal(err) } if err := nds.RunInTransaction(c, func(tc context.Context) error { return nds.DeleteMulti(tc, []*datastore.Key{key}) }, nil); err != nil { t.Fatal(err) } if err := nds.Get(c, key, &testEntity{}); err == nil { t.Fatal("expected no entity") } else if err != datastore.ErrNoSuchEntity { t.Fatal(err) } }
// New creates and returns a new blank account. It returns an error if an account // with the specified email address already exists. func New(ctx context.Context, email, password string) (*Account, error) { account := new(Account) account.Email = email account.CreatedAt = time.Now() if err := account.SetPassword(password); err != nil { return nil, err } err := nds.RunInTransaction(ctx, func(txCtx context.Context) error { dsKey := account.Key(txCtx) if err := nds.Get(txCtx, dsKey, account); err == nil { return ErrAccountExists } else if err != datastore.ErrNoSuchEntity { return err } _, err := nds.Put(txCtx, dsKey, account) return err }, nil) if err != nil { return nil, err } account.flag = camethroughus account.originalEmail = email return account, nil }
func TestIncrement(t *testing.T) { var wg sync.WaitGroup wg.Add(100) for i := 0; i < 100; i++ { go func() { time.Sleep((time.Duration(rand.Int63()%7) + 1) * time.Second) ctr, _ := NewCounter("", "foo", 50) if err := ctr.Increment(ctx, 1); err != nil { t.Errorf("Expected no error when incrementing counter 'foo' by 1, but got %s", err) } wg.Done() }() } wg.Wait() // now try in an existing transaction context; should error out ctr, _ := NewCounter("", "foo", 50) txErr := nds.RunInTransaction(ctx, func(txCtx context.Context) error { return ctr.Increment(txCtx, 1) }, nil) if txErr == nil { t.Errorf("Wanted an error calling Increment with a nested transaction context, but got no error") } }
// Increment adds delta to the counter. You can therefore decrement the counter // by supplying a negative number. // // You cannot call Increment inside a datastore transaction; for that, use IncrementX. func (c *Counter) Increment(ctx context.Context, delta int64) error { return nds.RunInTransaction(ctx, func(txCtx context.Context) error { return c.IncrementX(txCtx, delta) }, nil) }
func TestRunInTransaction(t *testing.T) { c, closeFunc := NewContext(t) defer closeFunc() type testEntity struct { Val int } key := datastore.NewKey(c, "Entity", "", 3, nil) keys := []*datastore.Key{key} entity := testEntity{42} entities := []testEntity{entity} if _, err := nds.PutMulti(c, keys, entities); err != nil { t.Fatal(err) } err := nds.RunInTransaction(c, func(tc context.Context) error { entities := make([]testEntity, 1, 1) if err := nds.GetMulti(tc, keys, entities); err != nil { t.Fatal(err) } entity := entities[0] if entity.Val != 42 { t.Fatalf("entity.Val != 42: %d", entity.Val) } entities[0].Val = 43 putKeys, err := nds.PutMulti(tc, keys, entities) if err != nil { t.Fatal(err) } else if len(putKeys) != 1 { t.Fatal("putKeys should be len 1") } else if !putKeys[0].Equal(key) { t.Fatal("keys not equal") } return nil }, nil) if err != nil { t.Fatal(err) } entities = make([]testEntity, 1, 1) if err := nds.GetMulti(c, keys, entities); err != nil { t.Fatal(err) } entity = entities[0] if entity.Val != 43 { t.Fatalf("entity.Val != 43: %d", entity.Val) } }
func TestTransactionOptions(t *testing.T) { c, closeFunc := NewContext(t, nil) defer closeFunc() type testEntity struct { Val int } opts := &datastore.TransactionOptions{XG: true} err := nds.RunInTransaction(c, func(tc context.Context) error { for i := 0; i < 4; i++ { key := datastore.NewIncompleteKey(tc, "Entity", nil) if _, err := nds.Put(tc, key, &testEntity{i}); err != nil { return err } } return nil }, opts) if err != nil { t.Fatal(err) } opts = &datastore.TransactionOptions{XG: false} err = nds.RunInTransaction(c, func(tc context.Context) error { for i := 0; i < 4; i++ { key := datastore.NewIncompleteKey(tc, "Entity", nil) if _, err := nds.Put(tc, key, &testEntity{i}); err != nil { return err } } return nil }, opts) if err == nil { t.Fatal("expected cross-group error") } }
// Creates and configures a store that stores entities in Google AppEngines memcache and datastore. // github.com/qedus/nds is used for strongly consistent automatic caching. func NewGaeStore(kind string, ctx context.Context, idf sus.IdFactory, vf sus.VersionFactory, ei sus.EntityInitializer) sus.Store { getKey := func(ctx context.Context, id string) *datastore.Key { return datastore.NewKey(ctx, kind, id, 0, nil) } getMulti := func(ids []string) (vs []sus.Version, err error) { count := len(ids) vs = make([]sus.Version, count, count) ks := make([]*datastore.Key, count, count) for i := 0; i < count; i++ { vs[i] = vf() ks[i] = getKey(ctx, ids[i]) } err = nds.GetMulti(ctx, ks, vs) return } putMulti := func(ids []string, vs []sus.Version) (err error) { count := len(ids) ks := make([]*datastore.Key, count, count) for i := 0; i < count; i++ { ks[i] = getKey(ctx, ids[i]) } _, err = nds.PutMulti(ctx, ks, vs) return } delMulti := func(ids []string) error { count := len(ids) ks := make([]*datastore.Key, count, count) for i := 0; i < count; i++ { ks[i] = getKey(ctx, ids[i]) } return nds.DeleteMulti(ctx, ks) } isNonExtantError := func(err error) bool { return err.Error() == datastore.ErrNoSuchEntity.Error() } rit := func(tran sus.Transaction) error { return nds.RunInTransaction(ctx, func(ctx context.Context) error { return tran() }, &datastore.TransactionOptions{XG: true}) } return sus.NewStore(getMulti, putMulti, delMulti, idf, vf, ei, isNonExtantError, rit) }
// TestClearNamespacedLocks tests to make sure that locks are cleared when // RunInTransaction is using a namespace. func TestClearNamespacedLocks(t *testing.T) { c, closeFunc := NewContext(t, nil) defer closeFunc() c, err := appengine.Namespace(c, "testnamespace") if err != nil { t.Fatal(err) } type testEntity struct { Val int } key := datastore.NewKey(c, "TestEntity", "", 1, nil) // Prime cache. if err := nds.Get(c, key, &testEntity{}); err == nil { t.Fatal("expected no such entity") } else if err != datastore.ErrNoSuchEntity { t.Fatal(err) } if err := nds.RunInTransaction(c, func(tc context.Context) error { if err := nds.Get(tc, key, &testEntity{}); err == nil { return errors.New("expected no such entity") } else if err != datastore.ErrNoSuchEntity { return err } if _, err := nds.Put(tc, key, &testEntity{3}); err != nil { return err } return nil }, nil); err != nil { t.Fatal(err) } entity := &testEntity{} if err := nds.Get(c, key, entity); err != nil { t.Fatal(err) } if entity.Val != 3 { t.Fatal("incorrect val") } }
// Save saves the account pointed to by account to the datastore. It modifies // account.LastUpdatedAt for convenience. It returns an error if the account cannot // be saved because it was not obtained through the API methods, or if the state of the // account in the datastore has changed in the interim. func Save(ctx context.Context, account *Account) error { if account.flag != camethroughus || account.Email != account.originalEmail { return ErrUnsaveableAccount } return nds.RunInTransaction(ctx, func(txCtx context.Context) error { if hasChanged, err := HasChanged(txCtx, account); err != nil && err != datastore.ErrNoSuchEntity { return err } else if hasChanged { return ErrConflict } account.LastUpdatedAt = time.Now() _, err := nds.Put(ctx, account.Key(ctx), account) return err }, nil) }
// ChangeEmail changes the email address of an account from oldEmail to newEmail. // It performs this operation atomically. func ChangeEmail(ctx context.Context, oldEmail, newEmail string) error { return nds.RunInTransaction(ctx, func(txCtx context.Context) error { // read out both the account at the old and the new email addresses var fromAccount, toAccount Account var errFrom, errTo error fromAccountKey := datastore.NewKey(txCtx, Entity, oldEmail, 0, nil) toAccountKey := datastore.NewKey(txCtx, Entity, newEmail, 0, nil) var s sync.WaitGroup s.Add(2) go func() { errFrom = nds.Get(txCtx, fromAccountKey, &fromAccount) s.Done() }() go func() { errTo = nds.Get(txCtx, toAccountKey, &toAccount) s.Done() }() s.Wait() if errFrom != nil { return errFrom } else if errTo != datastore.ErrNoSuchEntity { return ErrAccountExists } // at this point, we set FromAccount's email address to the new one fromAccount.Email = newEmail fromAccount.LastUpdatedAt = time.Now() s.Add(2) go func() { // delete the account at the old key errFrom = nds.Delete(txCtx, fromAccountKey) s.Done() }() go func() { // save the account at the new key _, errTo = nds.Put(txCtx, toAccountKey, &fromAccount) s.Done() }() s.Wait() if errFrom != nil { return errFrom } else if errTo != nil { return errTo } return nil }, xgTransaction) }