Example #1
0
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("")
}
Example #2
0
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)
	}
}
Example #3
0
// 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

}
Example #4
0
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")
	}

}
Example #5
0
// 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)

}
Example #6
0
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)
	}
}
Example #7
0
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")
	}

}
Example #8
0
File: gus.go Project: robsix/rps
// 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)
}
Example #9
0
// 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")
	}
}
Example #10
0
// 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)

}
Example #11
0
// 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)

}