| // Copyright 2011 Google Inc. All rights reserved. | |
| // Use of this source code is governed by the Apache 2.0 | |
| // license that can be found in the LICENSE file. | |
| package datastore | |
| import ( | |
| "context" | |
| "errors" | |
| "google.golang.org/appengine/internal" | |
| pb "google.golang.org/appengine/internal/datastore" | |
| ) | |
| func init() { | |
| internal.RegisterTransactionSetter(func(x *pb.Query, t *pb.Transaction) { | |
| x.Transaction = t | |
| }) | |
| internal.RegisterTransactionSetter(func(x *pb.GetRequest, t *pb.Transaction) { | |
| x.Transaction = t | |
| }) | |
| internal.RegisterTransactionSetter(func(x *pb.PutRequest, t *pb.Transaction) { | |
| x.Transaction = t | |
| }) | |
| internal.RegisterTransactionSetter(func(x *pb.DeleteRequest, t *pb.Transaction) { | |
| x.Transaction = t | |
| }) | |
| } | |
| // ErrConcurrentTransaction is returned when a transaction is rolled back due | |
| // to a conflict with a concurrent transaction. | |
| var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction") | |
| // RunInTransaction runs f in a transaction. It calls f with a transaction | |
| // context tc that f should use for all App Engine operations. | |
| // | |
| // If f returns nil, RunInTransaction attempts to commit the transaction, | |
| // returning nil if it succeeds. If the commit fails due to a conflicting | |
| // transaction, RunInTransaction retries f, each time with a new transaction | |
| // context. It gives up and returns ErrConcurrentTransaction after three | |
| // failed attempts. The number of attempts can be configured by specifying | |
| // TransactionOptions.Attempts. | |
| // | |
| // If f returns non-nil, then any datastore changes will not be applied and | |
| // RunInTransaction returns that same error. The function f is not retried. | |
| // | |
| // Note that when f returns, the transaction is not yet committed. Calling code | |
| // must be careful not to assume that any of f's changes have been committed | |
| // until RunInTransaction returns nil. | |
| // | |
| // Since f may be called multiple times, f should usually be idempotent. | |
| // datastore.Get is not idempotent when unmarshaling slice fields. | |
| // | |
| // Nested transactions are not supported; c may not be a transaction context. | |
| func RunInTransaction(c context.Context, f func(tc context.Context) error, opts *TransactionOptions) error { | |
| xg := false | |
| if opts != nil { | |
| xg = opts.XG | |
| } | |
| readOnly := false | |
| if opts != nil { | |
| readOnly = opts.ReadOnly | |
| } | |
| attempts := 3 | |
| if opts != nil && opts.Attempts > 0 { | |
| attempts = opts.Attempts | |
| } | |
| var t *pb.Transaction | |
| var err error | |
| for i := 0; i < attempts; i++ { | |
| if t, err = internal.RunTransactionOnce(c, f, xg, readOnly, t); err != internal.ErrConcurrentTransaction { | |
| return err | |
| } | |
| } | |
| return ErrConcurrentTransaction | |
| } | |
| // TransactionOptions are the options for running a transaction. | |
| type TransactionOptions struct { | |
| // XG is whether the transaction can cross multiple entity groups. In | |
| // comparison, a single group transaction is one where all datastore keys | |
| // used have the same root key. Note that cross group transactions do not | |
| // have the same behavior as single group transactions. In particular, it | |
| // is much more likely to see partially applied transactions in different | |
| // entity groups, in global queries. | |
| // It is valid to set XG to true even if the transaction is within a | |
| // single entity group. | |
| XG bool | |
| // Attempts controls the number of retries to perform when commits fail | |
| // due to a conflicting transaction. If omitted, it defaults to 3. | |
| Attempts int | |
| // ReadOnly controls whether the transaction is a read only transaction. | |
| // Read only transactions are potentially more efficient. | |
| ReadOnly bool | |
| } |