@@ -231,6 +231,8 @@ type SpannerConn interface {
231
231
// returned.
232
232
resetTransactionForRetry (ctx context.Context , errDuringCommit bool ) error
233
233
234
+ withTransactionCloseFunc (close func ())
235
+
234
236
// withTempTransactionOptions sets the TransactionOptions that should be used
235
237
// for the next read/write transaction. This method should only be called
236
238
// directly before starting a new read/write transaction.
@@ -257,8 +259,8 @@ type conn struct {
257
259
adminClient * adminapi.DatabaseAdminClient
258
260
connId string
259
261
logger * slog.Logger
260
- tx contextTransaction
261
- prevTx contextTransaction
262
+ tx * delegatingTransaction
263
+ prevTx * delegatingTransaction
262
264
resetForRetry bool
263
265
database string
264
266
@@ -531,7 +533,7 @@ func (c *conn) InDDLBatch() bool {
531
533
}
532
534
533
535
func (c * conn ) InDMLBatch () bool {
534
- return (c .batch != nil && c .batch .tp == parser .BatchTypeDml ) || (c .inReadWriteTransaction () && c .tx .( * readWriteTransaction ). batch != nil )
536
+ return (c .batch != nil && c .batch .tp == parser .BatchTypeDml ) || (c .inTransaction () && c .tx .IsInBatch () )
535
537
}
536
538
537
539
func (c * conn ) GetBatchedStatements () []spanner.Statement {
@@ -567,9 +569,6 @@ func (c *conn) startBatchDML(automatic bool) (driver.Result, error) {
567
569
if c .batch != nil {
568
570
return nil , spanner .ToSpannerError (status .Errorf (codes .FailedPrecondition , "This connection already has an active batch." ))
569
571
}
570
- if c .inReadOnlyTransaction () {
571
- return nil , spanner .ToSpannerError (status .Errorf (codes .FailedPrecondition , "This connection has an active read-only transaction. Read-only transactions cannot execute DML batches." ))
572
- }
573
572
c .logger .Debug ("starting dml batch outside transaction" )
574
573
c .batch = & batch {tp : parser .BatchTypeDml , options : execOptions }
575
574
return driver .ResultNoRows , nil
@@ -655,8 +654,8 @@ func (c *conn) execBatchDML(ctx context.Context, statements []spanner.Statement,
655
654
656
655
var affected []int64
657
656
var err error
658
- if c .inTransaction () {
659
- tx , ok := c .tx .(* readWriteTransaction )
657
+ if c .inTransaction () && c . tx . contextTransaction != nil {
658
+ tx , ok := c .tx .contextTransaction . (* readWriteTransaction )
660
659
if ! ok {
661
660
return nil , status .Errorf (codes .FailedPrecondition , "connection is in a transaction that is not a read/write transaction" )
662
661
}
@@ -944,7 +943,7 @@ func (c *conn) execContext(ctx context.Context, query string, execOptions *ExecO
944
943
}
945
944
946
945
// Start an automatic DML batch.
947
- if c .AutoBatchDml () && ! c .inBatch () && c .inReadWriteTransaction () {
946
+ if c .AutoBatchDml () && ! c .inBatch () && c .inTransaction () && statementInfo . StatementType == parser . StatementTypeDml {
948
947
if _ , err := c .startBatchDML ( /* automatic = */ true ); err != nil {
949
948
return nil , err
950
949
}
@@ -1041,14 +1040,14 @@ func (c *conn) resetTransactionForRetry(ctx context.Context, errDuringCommit boo
1041
1040
return c .tx .resetForRetry (ctx )
1042
1041
}
1043
1042
1043
+ func (c * conn ) withTransactionCloseFunc (close func ()) {
1044
+ c .tempTransactionCloseFunc = close
1045
+ }
1046
+
1044
1047
func (c * conn ) withTempTransactionOptions (options * ReadWriteTransactionOptions ) {
1045
1048
if options == nil {
1046
1049
return
1047
1050
}
1048
- c .tempTransactionCloseFunc = options .close
1049
- // Start a transaction for the connection state, so we can set the transaction options
1050
- // as local options in the current transaction.
1051
- _ = c .state .Begin ()
1052
1051
if options .DisableInternalRetries {
1053
1052
_ = propertyRetryAbortsInternally .SetLocalValue (c .state , ! options .DisableInternalRetries )
1054
1053
}
@@ -1102,10 +1101,6 @@ func (c *conn) withTempReadOnlyTransactionOptions(options *ReadOnlyTransactionOp
1102
1101
if options == nil {
1103
1102
return
1104
1103
}
1105
- c .tempTransactionCloseFunc = options .close
1106
- // Start a transaction for the connection state, so we can set the transaction options
1107
- // as local options in the current transaction.
1108
- _ = c .state .Begin ()
1109
1104
if options .BeginTransactionOption != spanner .DefaultBeginTransaction {
1110
1105
_ = propertyBeginTransactionOption .SetLocalValue (c .state , options .BeginTransactionOption )
1111
1106
}
@@ -1122,10 +1117,6 @@ func (c *conn) withTempBatchReadOnlyTransactionOptions(options *BatchReadOnlyTra
1122
1117
if options == nil {
1123
1118
return
1124
1119
}
1125
- c .tempTransactionCloseFunc = options .close
1126
- // Start a transaction for the connection state, so we can set the transaction options
1127
- // as local options in the current transaction.
1128
- _ = c .state .Begin ()
1129
1120
if options .TimestampBound .String () != "(strong)" {
1130
1121
_ = propertyReadOnlyStaleness .SetLocalValue (c .state , options .TimestampBound )
1131
1122
}
@@ -1139,9 +1130,9 @@ func (c *conn) getBatchReadOnlyTransactionOptions() BatchReadOnlyTransactionOpti
1139
1130
// It is exported for internal reasons, and may receive breaking changes without prior notice.
1140
1131
//
1141
1132
// BeginReadOnlyTransaction starts a new read-only transaction on this connection.
1142
- func (c * conn ) BeginReadOnlyTransaction (ctx context.Context , options * ReadOnlyTransactionOptions ) (driver.Tx , error ) {
1133
+ func (c * conn ) BeginReadOnlyTransaction (ctx context.Context , options * ReadOnlyTransactionOptions , close func ()) (driver.Tx , error ) {
1134
+ tx , err := c .beginTx (ctx , driver.TxOptions {ReadOnly : true }, close )
1143
1135
c .withTempReadOnlyTransactionOptions (options )
1144
- tx , err := c .BeginTx (ctx , driver.TxOptions {ReadOnly : true })
1145
1136
if err != nil {
1146
1137
return nil , err
1147
1138
}
@@ -1152,9 +1143,9 @@ func (c *conn) BeginReadOnlyTransaction(ctx context.Context, options *ReadOnlyTr
1152
1143
// It is exported for internal reasons, and may receive breaking changes without prior notice.
1153
1144
//
1154
1145
// BeginReadWriteTransaction starts a new read/write transaction on this connection.
1155
- func (c * conn ) BeginReadWriteTransaction (ctx context.Context , options * ReadWriteTransactionOptions ) (driver.Tx , error ) {
1146
+ func (c * conn ) BeginReadWriteTransaction (ctx context.Context , options * ReadWriteTransactionOptions , close func ()) (driver.Tx , error ) {
1147
+ tx , err := c .beginTx (ctx , driver.TxOptions {}, close )
1156
1148
c .withTempTransactionOptions (options )
1157
- tx , err := c .BeginTx (ctx , driver.TxOptions {})
1158
1149
if err != nil {
1159
1150
return nil , err
1160
1151
}
@@ -1177,19 +1168,6 @@ func (c *conn) beginTx(ctx context.Context, driverOpts driver.TxOptions, closeFu
1177
1168
c .resetForRetry = false
1178
1169
return c .tx , nil
1179
1170
}
1180
- // Also start a transaction on the ConnectionState if the BeginTx call was successful.
1181
- defer func () {
1182
- if c .tx != nil {
1183
- _ = c .state .Begin ()
1184
- } else {
1185
- // Rollback in case the connection state transaction was started before this function
1186
- // was called, for example if the caller set temporary transaction options.
1187
- _ = c .state .Rollback ()
1188
- }
1189
- }()
1190
-
1191
- readOnlyTxOpts := c .getReadOnlyTransactionOptions ()
1192
- batchReadOnlyTxOpts := c .getBatchReadOnlyTransactionOptions ()
1193
1171
if c .inTransaction () {
1194
1172
return nil , spanner .ToSpannerError (status .Errorf (codes .FailedPrecondition , "already in a transaction" ))
1195
1173
}
@@ -1227,94 +1205,105 @@ func (c *conn) beginTx(ctx context.Context, driverOpts driver.TxOptions, closeFu
1227
1205
if closeFunc == nil {
1228
1206
closeFunc = func () {}
1229
1207
}
1208
+ if err := c .state .Begin (); err != nil {
1209
+ return nil , err
1210
+ }
1211
+ c .clearCommitResponse ()
1230
1212
1213
+ if isolationLevelFromTxOpts != spannerpb .TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED {
1214
+ _ = propertyIsolationLevel .SetLocalValue (c .state , sql .IsolationLevel (driverOpts .Isolation ))
1215
+ }
1216
+ // TODO: Figure out how to distinguish between 'use the default' and 'use read/write'.
1231
1217
if driverOpts .ReadOnly {
1218
+ _ = propertyTransactionReadOnly .SetLocalValue (c .state , true )
1219
+ }
1220
+ if batchReadOnly {
1221
+ _ = propertyTransactionBatchReadOnly .SetLocalValue (c .state , true )
1222
+ }
1223
+ if disableRetryAborts {
1224
+ _ = propertyRetryAbortsInternally .SetLocalValue (c .state , false )
1225
+ }
1226
+
1227
+ c .tx = & delegatingTransaction {
1228
+ conn : c ,
1229
+ ctx : ctx ,
1230
+ close : func (result txResult ) {
1231
+ closeFunc ()
1232
+ if result == txResultCommit {
1233
+ _ = c .state .Commit ()
1234
+ } else {
1235
+ _ = c .state .Rollback ()
1236
+ }
1237
+ c .tx = nil
1238
+ },
1239
+ }
1240
+ return c .tx , nil
1241
+ }
1242
+
1243
+ func (c * conn ) activateTransaction () (contextTransaction , error ) {
1244
+ closeFunc := c .tx .close
1245
+ if propertyTransactionReadOnly .GetValueOrDefault (c .state ) {
1232
1246
var logger * slog.Logger
1233
1247
var ro * spanner.ReadOnlyTransaction
1234
1248
var bo * spanner.BatchReadOnlyTransaction
1235
- if batchReadOnly {
1249
+ if propertyTransactionBatchReadOnly . GetValueOrDefault ( c . state ) {
1236
1250
logger = c .logger .With ("tx" , "batchro" )
1237
1251
var err error
1238
1252
// BatchReadOnly transactions (currently) do not support inline-begin.
1239
1253
// This means that the transaction options must be supplied here, and not through a callback.
1240
- bo , err = c .client .BatchReadOnlyTransaction (ctx , batchReadOnlyTxOpts . TimestampBound )
1254
+ bo , err = c .client .BatchReadOnlyTransaction (c . tx . ctx , propertyReadOnlyStaleness . GetValueOrDefault ( c . state ) )
1241
1255
if err != nil {
1242
1256
return nil , err
1243
1257
}
1244
1258
ro = & bo .ReadOnlyTransaction
1245
1259
} else {
1246
1260
logger = c .logger .With ("tx" , "ro" )
1247
- ro = c .client .ReadOnlyTransaction ().WithBeginTransactionOption (readOnlyTxOpts .BeginTransactionOption )
1261
+ beginTxOpt := c .convertDefaultBeginTransactionOption (propertyBeginTransactionOption .GetValueOrDefault (c .state ))
1262
+ ro = c .client .ReadOnlyTransaction ().WithBeginTransactionOption (beginTxOpt )
1248
1263
}
1249
- c . tx = & readOnlyTransaction {
1264
+ return & readOnlyTransaction {
1250
1265
roTx : ro ,
1251
1266
boTx : bo ,
1252
1267
logger : logger ,
1253
- close : func (result txResult ) {
1254
- closeFunc ()
1255
- if result == txResultCommit {
1256
- _ = c .state .Commit ()
1257
- } else {
1258
- _ = c .state .Rollback ()
1259
- }
1260
- c .tx = nil
1261
- },
1268
+ close : closeFunc ,
1262
1269
timestampBoundCallback : func () spanner.TimestampBound {
1263
1270
return propertyReadOnlyStaleness .GetValueOrDefault (c .state )
1264
1271
},
1265
- }
1266
- return c .tx , nil
1272
+ }, nil
1267
1273
}
1268
1274
1269
- // These options are only used to determine how to start the transaction.
1270
- // All other options are fetched in a callback that is called when the transaction is actually started.
1271
- // That callback reads all transaction options from the connection state at that moment. This allows
1272
- // applications to execute a series of statement like this:
1273
- // BEGIN TRANSACTION;
1274
- // SET LOCAL transaction_tag='my_tag';
1275
- // SET LOCAL commit_priority=LOW;
1276
- // INSERT INTO my_table ... -- This starts the transaction with the options above included.
1277
1275
opts := spanner.TransactionOptions {}
1278
1276
opts .BeginTransactionOption = c .convertDefaultBeginTransactionOption (propertyBeginTransactionOption .GetValueOrDefault (c .state ))
1279
1277
1280
- tx , err := spanner .NewReadWriteStmtBasedTransactionWithCallbackForOptions (ctx , c .client , opts , func () spanner.TransactionOptions {
1278
+ tx , err := spanner .NewReadWriteStmtBasedTransactionWithCallbackForOptions (c . tx . ctx , c .client , opts , func () spanner.TransactionOptions {
1281
1279
defer func () {
1282
1280
// Reset the transaction_tag after starting the transaction.
1283
1281
_ = propertyTransactionTag .ResetValue (c .state , connectionstate .ContextUser )
1284
1282
}()
1285
- return c .effectiveTransactionOptions (isolationLevelFromTxOpts , c .options ( /*reset=*/ true ))
1283
+ return c .effectiveTransactionOptions (spannerpb . TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED , c .options ( /*reset=*/ true ))
1286
1284
})
1287
1285
if err != nil {
1288
1286
return nil , err
1289
1287
}
1290
1288
logger := c .logger .With ("tx" , "rw" )
1291
- c . tx = & readWriteTransaction {
1292
- ctx : ctx ,
1289
+ return & readWriteTransaction {
1290
+ ctx : c . tx . ctx ,
1293
1291
conn : c ,
1294
1292
logger : logger ,
1295
1293
rwTx : tx ,
1296
1294
close : func (result txResult , commitResponse * spanner.CommitResponse , commitErr error ) {
1297
- closeFunc ()
1298
1295
c .prevTx = c .tx
1299
- c .tx = nil
1300
1296
if commitErr == nil {
1301
1297
c .setCommitResponse (commitResponse )
1302
- if result == txResultCommit {
1303
- _ = c .state .Commit ()
1304
- } else {
1305
- _ = c .state .Rollback ()
1306
- }
1298
+ closeFunc (result )
1307
1299
} else {
1308
- _ = c . state . Rollback ( )
1300
+ closeFunc ( txResultRollback )
1309
1301
}
1310
1302
},
1311
- // Disable internal retries if any of these options have been set.
1312
1303
retryAborts : sync .OnceValue (func () bool {
1313
- return c .RetryAbortsInternally () && ! disableRetryAborts
1304
+ return c .RetryAbortsInternally ()
1314
1305
}),
1315
- }
1316
- c .clearCommitResponse ()
1317
- return c .tx , nil
1306
+ }, nil
1318
1307
}
1319
1308
1320
1309
func (c * conn ) effectiveTransactionOptions (isolationLevelFromTxOpts spannerpb.TransactionOptions_IsolationLevel , execOptions * ExecOptions ) spanner.TransactionOptions {
@@ -1340,22 +1329,6 @@ func (c *conn) inTransaction() bool {
1340
1329
return c .tx != nil
1341
1330
}
1342
1331
1343
- func (c * conn ) inReadOnlyTransaction () bool {
1344
- if c .tx != nil {
1345
- _ , ok := c .tx .(* readOnlyTransaction )
1346
- return ok
1347
- }
1348
- return false
1349
- }
1350
-
1351
- func (c * conn ) inReadWriteTransaction () bool {
1352
- if c .tx != nil {
1353
- _ , ok := c .tx .(* readWriteTransaction )
1354
- return ok
1355
- }
1356
- return false
1357
- }
1358
-
1359
1332
// Commit is not part of the public API of the database/sql driver.
1360
1333
// It is exported for internal reasons, and may receive breaking changes without prior notice.
1361
1334
//
0 commit comments