@@ -1140,14 +1140,56 @@ type Transaction struct {
11401140 mutations []map [string ]any
11411141}
11421142
1143+ // TransactionOption configures transaction behavior.
1144+ type TransactionOption interface {
1145+ apply (* transactionSettings )
1146+ }
1147+
1148+ type transactionSettings struct {
1149+ readTime time.Time
1150+ maxAttempts int
1151+ }
1152+
1153+ type maxAttemptsOption int
1154+
1155+ func (o maxAttemptsOption ) apply (s * transactionSettings ) {
1156+ s .maxAttempts = int (o )
1157+ }
1158+
1159+ // MaxAttempts returns a TransactionOption that specifies the maximum number
1160+ // of times a transaction should be attempted before giving up.
1161+ func MaxAttempts (n int ) TransactionOption {
1162+ return maxAttemptsOption (n )
1163+ }
1164+
1165+ type readTimeOption struct {
1166+ t time.Time
1167+ }
1168+
1169+ func (o readTimeOption ) apply (s * transactionSettings ) {
1170+ s .readTime = o .t
1171+ }
1172+
1173+ // WithReadTime returns a TransactionOption that sets a specific timestamp
1174+ // at which to read data, enabling reading from a particular snapshot in time.
1175+ func WithReadTime (t time.Time ) TransactionOption {
1176+ return readTimeOption {t : t }
1177+ }
1178+
11431179// RunInTransaction runs a function in a transaction.
11441180// The function should use the transaction's Get and Put methods.
11451181// API compatible with cloud.google.com/go/datastore.
1146- func (c * Client ) RunInTransaction (ctx context.Context , f func (* Transaction ) error ) (* Commit , error ) {
1147- const maxTxRetries = 3
1182+ func (c * Client ) RunInTransaction (ctx context.Context , f func (* Transaction ) error , opts ... TransactionOption ) (* Commit , error ) {
1183+ settings := transactionSettings {
1184+ maxAttempts : 3 , // default
1185+ }
1186+ for _ , opt := range opts {
1187+ opt .apply (& settings )
1188+ }
1189+
11481190 var lastErr error
11491191
1150- for attempt := range maxTxRetries {
1192+ for attempt := range settings . maxAttempts {
11511193 token , err := auth .AccessToken (ctx )
11521194 if err != nil {
11531195 return nil , fmt .Errorf ("failed to get access token: %w" , err )
@@ -1159,6 +1201,19 @@ func (c *Client) RunInTransaction(ctx context.Context, f func(*Transaction) erro
11591201 reqBody ["databaseId" ] = c .databaseID
11601202 }
11611203
1204+ // Add transaction options if needed
1205+ if ! settings .readTime .IsZero () {
1206+ reqBody ["transactionOptions" ] = map [string ]any {
1207+ "readOnly" : map [string ]any {
1208+ "readTime" : settings .readTime .Format (time .RFC3339Nano ),
1209+ },
1210+ }
1211+ } else {
1212+ reqBody ["transactionOptions" ] = map [string ]any {
1213+ "readWrite" : map [string ]any {},
1214+ }
1215+ }
1216+
11621217 jsonData , err := json .Marshal (reqBody )
11631218 if err != nil {
11641219 return nil , err
@@ -1237,13 +1292,13 @@ func (c *Client) RunInTransaction(ctx context.Context, f func(*Transaction) erro
12371292 lastErr = err
12381293 c .logger .Warn ("transaction aborted, will retry" ,
12391294 "attempt" , attempt + 1 ,
1240- "max_attempts" , maxTxRetries ,
1295+ "max_attempts" , settings . maxAttempts ,
12411296 "has_409" , is409 ,
12421297 "has_ABORTED" , isAborted ,
12431298 "error" , err )
12441299
12451300 // Exponential backoff: 100ms, 200ms, 400ms
1246- if attempt < maxTxRetries - 1 {
1301+ if attempt < settings . maxAttempts - 1 {
12471302 backoffMS := 100 * (1 << attempt )
12481303 c .logger .Debug ("sleeping before retry" , "backoff_ms" , backoffMS )
12491304 time .Sleep (time .Duration (backoffMS ) * time .Millisecond )
@@ -1256,7 +1311,7 @@ func (c *Client) RunInTransaction(ctx context.Context, f func(*Transaction) erro
12561311 return nil , err
12571312 }
12581313
1259- return nil , fmt .Errorf ("transaction failed after %d attempts: %w" , maxTxRetries , lastErr )
1314+ return nil , fmt .Errorf ("transaction failed after %d attempts: %w" , settings . maxAttempts , lastErr )
12601315}
12611316
12621317// Get retrieves an entity within the transaction.
0 commit comments