1

I have a User entity containing an Email field. The User entity id is a ULID, because I want to allow users to change their email addresses, but I want to ensure that the email address is unique on both a CREATE and an UPDATE.

I am using Datastore transactions. This is a code fragment:

ctx := context.Background()
k := datastore.NameKey("User", user.ID, nil)
_, err := client.RunInTransaction(ctx, func(t *datastore.Transaction) error {
    // other stuff that needs to be in transaction
    _, err = t.Put(k, user)
    return err
})

return err

The Email field is indexed. Is there any way to search the User entity for the current user's email address as part of the transaction?

*datastore.Transaction does not have a GetAll method, so I cannot run a query like this:

datastore.NewQuery("User").Filter("Email =", user.Email)

I'm afraid that using

client.GetAll(ctx, q, nil)

will not guarantee isolation within the transaction.

Ralph
  • 31,584
  • 38
  • 145
  • 282
  • https://godoc.org/cloud.google.com/go/datastore#Transaction.Get Please note the comment about the isolation level as well. – Gavin Aug 22 '17 at 15:04
  • > All reads performed during the transaction will come from a single consistent snapshot. It is not clear to me that the isolation applies if the query is not made through `t`. Maybe I'm overthinking it? – Ralph Aug 22 '17 at 15:06
  • I'm not really familiar with datastore. Are you able to use the `Get` method to get a user by email in your scenario? Or will that only work for the ID field? – Gavin Aug 22 '17 at 15:09
  • The `Get` expects a key, not a query. – Ralph Aug 22 '17 at 15:10
  • Right, can you make a key `emailKey := datastore.NameKey("Email", user.Email, nil)`? – Gavin Aug 22 '17 at 15:11
  • No. I do not have an entity (i.e. "table") named `Email`. – Ralph Aug 22 '17 at 15:12

1 Answers1

4

The short answer is no, you cannot use a query as part of a transaction unless you are querying a specific entity group. Global queries are alway eventually consistent. However, to put everything in a single entity group would likely limit write throughput too much.

A workaround is you can have another Kind with entities that map email addresses to users. Then you can, in a transaction, check the email Entity and if it doesn't exist or it points to a bad location, set the email Entity and the user Entity all as a single transaction.

Stephen Weinberg
  • 51,320
  • 14
  • 134
  • 113
  • Ok. Thanks. I was thinking that the answer would be something along those lines. I guess when a user changes his/her email, I would also delete the "row" from `Email` that corresponds to the old value. – Ralph Aug 22 '17 at 15:14