// Save changes the application configuration to // the values in conf. All HTTP requests subsequent to this one // are guaranteed to use the new values in their configuration. // // Note that subsequent calls to Get with the same request context // will continue to retrieve the old version of the configuration. // // As a special case, calling Save with a *config.Config will replace // the entire contents of the configuration with the contents of Config. func Save(ctx context.Context, conf interface{}) error { if typedConfig, ok := conf.(*Config); ok { pl := datastore.PropertyList(*typedConfig) replaceKey := datastore.NewKey(ctx, Entity, Entity, 0, nil) _, replaceErr := nds.Put(ctx, replaceKey, &pl) return replaceErr } return datastore.RunInTransaction(ctx, func(txCtx context.Context) error { props := datastore.PropertyList{} key := datastore.NewKey(txCtx, Entity, Entity, 0, nil) if err := nds.Get(txCtx, key, &props); err != nil && err != datastore.ErrNoSuchEntity { return err } // merge existing config with the new values if newProps, err := datastore.SaveStruct(conf); err != nil { return err } else { for _, newProp := range newProps { newProp.NoIndex = true replacing := false for _, prop := range props { // make sure NoIndex is set prop.NoIndex = true if prop.Name == newProp.Name { replacing = true prop.Value = newProp.Value break } } if !replacing { // append props = append(props, newProp) } } } _, err := nds.Put(txCtx, key, &props) return err }, nil) }
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) } }
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("") }
// 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 TestPutDatastoreMultiError(t *testing.T) { c, closeFunc := NewContext(t) defer closeFunc() type testEntity struct { IntVal int } expectedErr := errors.New("expected error") nds.SetDatastorePutMulti(func(c context.Context, keys []*datastore.Key, vals interface{}) ([]*datastore.Key, error) { return nil, appengine.MultiError{expectedErr} }) defer func() { nds.SetDatastorePutMulti(datastore.PutMulti) }() key := datastore.NewKey(c, "Test", "", 1, nil) val := &testEntity{42} if _, err := nds.Put(c, key, val); err == nil { t.Fatal("expected error") } else if err != expectedErr { t.Fatal("should be expectedErr") } }
func warmupHandler(c *echo.Context) error { if appengine.IsDevAppServer() { photographers := []Photographer{ {1, "Mr Canon"}, {2, "Miss Nikon"}, {3, "Mrs Pentax"}, {4, "Ms Sony"}, } // create some dummy data for m := 1; m <= 12; m++ { for d := 1; d < 28; d++ { taken := time.Date(2015, time.Month(m), d, 12, 0, 0, 0, time.UTC) id := rand.Int31n(4) photographer := photographers[id] p := Photo{ Photographer: photographer, Uploaded: time.Now().UTC(), Width: 8000, Height: 6000, Taken: taken, } k := datastore.NewIncompleteKey(c, "photo", nil) nds.Put(c, k, &p) } } } return c.NoContent(http.StatusOK) }
func TestPutNilArgs(t *testing.T) { c, closeFunc := NewContext(t) defer closeFunc() if _, err := nds.Put(c, nil, nil); err == nil { t.Fatal("expected error") } }
// TestGetNamespacedKey ensures issue https://goo.gl/rXU8nK is fixed so that // memcache uses the namespace from the key instead of the context. func TestGetNamespacedKey(t *testing.T) { c, closeFunc := NewContext(t) defer closeFunc() const intVal = int64(12) type testEntity struct { IntVal int64 } namespacedCtx, err := appengine.Namespace(c, "keyNamespace") if err != nil { t.Fatal(err) } key := datastore.NewKey(c, "Entity", "", 1, nil) namespacedKey := datastore.NewKey(namespacedCtx, "Entity", "", key.IntID(), nil) entity := &testEntity{intVal} if namespacedKey, err = nds.Put(c, namespacedKey, entity); err != nil { t.Fatal(err) } // Prime cache. if err := nds.Get(namespacedCtx, namespacedKey, &testEntity{}); err != nil { t.Fatal(err) } // Ensure that we get a value back from the cache by checking if the // datastore is called at all. entityFromCache := true nds.SetDatastoreGetMulti(func(c context.Context, keys []*datastore.Key, vals interface{}) error { if len(keys) != 0 { entityFromCache = false } return nil }) if err := nds.Get(c, namespacedKey, &testEntity{}); err != nil { t.Fatal(err) } nds.SetDatastoreGetMulti(datastore.GetMulti) if !entityFromCache { t.Fatal("entity not obtained from cache") } if err := nds.Delete(namespacedCtx, namespacedKey); err != nil { t.Fatal(err) } entity = &testEntity{} if err := nds.Get(c, namespacedKey, entity); err == nil { t.Fatalf("expected no such entity error but got %+v", entity) } else if err != datastore.ErrNoSuchEntity { t.Fatal(err) } }
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") } }
// 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") } }
// IncrementX is the version of Increment you should call if you wish to increment a // counter inside a transaction. func (c *Counter) IncrementX(txCtx context.Context, delta int64) error { val := count{} // pick a key at random and alter its value by delta key := datastore.NewKey(txCtx, c.entity, c.shardKeys[rand.Int63()%int64(c.shardCount)], 0, nil) if err := nds.Get(txCtx, key, &val); err != nil && err != datastore.ErrNoSuchEntity { return err } val.C += delta if _, err := nds.Put(txCtx, key, &val); err != nil { return err } return nil }
// 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) }
func warmupHandler(c *echo.Context) error { if appengine.IsDevAppServer() { k := datastore.NewKey(c, "photo", "", 1, nil) p := new(Photo) err := nds.Get(c, k, p) if err != datastore.ErrNoSuchEntity { return c.NoContent(http.StatusOK) } photographers := []Photographer{ {1, "Mr Canon"}, {2, "Miss Nikon"}, {3, "Mrs Pentax"}, {4, "Ms Sony"}, } // create some dummy data var id int64 for m := 1; m <= 12; m++ { for d := 1; d < 28; d++ { taken := time.Date(2015, time.Month(m), d, 12, 0, 0, 0, time.UTC) photographer := photographers[rand.Int31n(4)] p = &Photo{ Photographer: photographer, Uploaded: time.Now().UTC(), Width: 8000, Height: 6000, Taken: taken, } id++ k = datastore.NewKey(c, "photo", "", id, nil) nds.Put(c, k, p) } } } return c.NoContent(http.StatusOK) }
func TestGetSliceProperty(t *testing.T) { c, closeFunc := NewContext(t) defer closeFunc() type testEntity struct { IntVals []int64 } key := datastore.NewKey(c, "Entity", "", 1, nil) intVals := []int64{0, 1, 2, 3} val := &testEntity{intVals} if _, err := nds.Put(c, key, val); err != nil { t.Fatal(err) } // Get from datastore. newVal := &testEntity{} if err := nds.Get(c, key, newVal); err != nil { t.Fatal(err) } if !reflect.DeepEqual(val.IntVals, intVals) { t.Fatal("slice properties not equal", val.IntVals) } // Get from memcache. newVal = &testEntity{} if err := nds.Get(c, key, newVal); err != nil { t.Fatal(err) } if !reflect.DeepEqual(val.IntVals, intVals) { t.Fatal("slice properties not equal", val.IntVals) } }
// 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) }
func TestPutGetDelete(t *testing.T) { c, closeFunc := NewContext(t) defer closeFunc() type testEntity struct { IntVal int } // Check we set memcahce, put datastore and delete memcache. seq := make(chan string, 3) nds.SetMemcacheSetMulti(func(c context.Context, items []*memcache.Item) error { seq <- "memcache.SetMulti" return memcache.SetMulti(c, items) }) nds.SetDatastorePutMulti(func(c context.Context, keys []*datastore.Key, vals interface{}) ([]*datastore.Key, error) { seq <- "datastore.PutMulti" return datastore.PutMulti(c, keys, vals) }) nds.SetMemcacheDeleteMulti(func(c context.Context, keys []string) error { seq <- "memcache.DeleteMulti" close(seq) return memcache.DeleteMulti(c, keys) }) incompleteKey := datastore.NewIncompleteKey(c, "Entity", nil) key, err := nds.Put(c, incompleteKey, &testEntity{43}) if err != nil { t.Fatal(err) } nds.SetMemcacheSetMulti(memcache.SetMulti) nds.SetDatastorePutMulti(datastore.PutMulti) nds.SetMemcacheDeleteMulti(memcache.DeleteMulti) if s := <-seq; s != "memcache.SetMulti" { t.Fatal("memcache.SetMulti not", s) } if s := <-seq; s != "datastore.PutMulti" { t.Fatal("datastore.PutMulti not", s) } if s := <-seq; s != "memcache.DeleteMulti" { t.Fatal("memcache.DeleteMulti not", s) } // Check chan is closed. <-seq if key.Incomplete() { t.Fatal("Key is incomplete") } te := &testEntity{} if err := nds.Get(c, key, te); err != nil { t.Fatal(err) } if te.IntVal != 43 { t.Fatal("te.Val != 43", te.IntVal) } // Get from cache. te = &testEntity{} if err := nds.Get(c, key, te); err != nil { t.Fatal(err) } if te.IntVal != 43 { t.Fatal("te.Val != 43", te.IntVal) } // Change value. if _, err := nds.Put(c, key, &testEntity{64}); err != nil { t.Fatal(err) } // Get from cache. te = &testEntity{} if err := nds.Get(c, key, te); err != nil { t.Fatal(err) } if te.IntVal != 64 { t.Fatal("te.Val != 64", te.IntVal) } if err := nds.Delete(c, key); err != nil { t.Fatal(err) } if err := nds.Get(c, key, &testEntity{}); err != datastore.ErrNoSuchEntity { t.Fatal("expected datastore.ErrNoSuchEntity") } }