Skip to content

Commit 9a4249b

Browse files
committed
Merge branch 'main' into spanner-lib
2 parents e83455c + 570063a commit 9a4249b

19 files changed

+393
-154
lines changed

aborted_transactions_test.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ func TestQueryWithError_CommitAborted(t *testing.T) {
325325
server.PutExecutionTime(testutil.MethodCommitTransaction, testutil.SimulatedExecutionTime{
326326
Errors: []error{status.Error(codes.Aborted, "Aborted")},
327327
})
328-
}, codes.NotFound, 0, 2, 2)
328+
}, codes.NotFound, 0, 3, 2)
329329
}
330330

331331
func TestQueryWithErrorHalfway_CommitAborted(t *testing.T) {
@@ -1080,7 +1080,7 @@ func TestBatchUpdateAbortedWithError_DifferentErrorDuringRetry(t *testing.T) {
10801080
t.Fatalf("dml statement failed: %v", err)
10811081
}
10821082
if _, err := tx.ExecContext(ctx, "RUN BATCH"); spanner.ErrCode(err) != codes.NotFound {
1083-
t.Fatalf("error code mismatch\nGot: %v\nWant: %v", spanner.ErrCode(err), codes.NotFound)
1083+
t.Fatalf("error code mismatch\n Got: %v\nWant: %v", spanner.ErrCode(err), codes.NotFound)
10841084
}
10851085

10861086
// Remove the error for the DML statement and cause a retry. The missing
@@ -1094,19 +1094,37 @@ func TestBatchUpdateAbortedWithError_DifferentErrorDuringRetry(t *testing.T) {
10941094
})
10951095
err = tx.Commit()
10961096
if err != ErrAbortedDueToConcurrentModification {
1097-
t.Fatalf("commit error mismatch\nGot: %v\nWant: %v", err, ErrAbortedDueToConcurrentModification)
1097+
t.Fatalf("commit error mismatch\n Got: %v\nWant: %v", err, ErrAbortedDueToConcurrentModification)
10981098
}
10991099
reqs := drainRequestsFromServer(server.TestSpanner)
11001100
execReqs := requestsOfType(reqs, reflect.TypeOf(&sppb.ExecuteBatchDmlRequest{}))
1101-
if g, w := len(execReqs), 2; g != w {
1102-
t.Fatalf("batch request count mismatch\nGot: %v\nWant: %v", g, w)
1101+
// There are 3 ExecuteBatchDmlRequests sent to Spanner:
1102+
// 1. An initial attempt with a BeginTransaction RPC, but this returns a NotFound error.
1103+
// This causes the transaction to be retried with an explicit BeginTransaction request.
1104+
// 2. Another attempt with a transaction ID.
1105+
// 3. A third attempt after the initial transaction is aborted.
1106+
if g, w := len(execReqs), 3; g != w {
1107+
t.Fatalf("batch request count mismatch\n Got: %v\nWant: %v", g, w)
11031108
}
11041109
commitReqs := requestsOfType(reqs, reflect.TypeOf(&sppb.CommitRequest{}))
11051110
// The commit should be attempted only once.
11061111
if g, w := len(commitReqs), 1; g != w {
1107-
t.Fatalf("commit request count mismatch\nGot: %v\nWant: %v", g, w)
1112+
t.Fatalf("commit request count mismatch\n Got: %v\nWant: %v", g, w)
1113+
}
1114+
// The first ExecuteBatchDml request should try to use an inline-begin.
1115+
// After that, we should have two BeginTransaction requests.
1116+
req1 := execReqs[0].(*sppb.ExecuteBatchDmlRequest)
1117+
if req1.GetTransaction() == nil || req1.GetTransaction().GetBegin() == nil {
1118+
t.Fatal("the first ExecuteBatchDmlRequest should have a BeginTransaction")
1119+
}
1120+
req2 := execReqs[1].(*sppb.ExecuteBatchDmlRequest)
1121+
if req2.GetTransaction() == nil || req2.GetTransaction().GetId() == nil {
1122+
t.Fatal("the second ExecuteBatchDmlRequest should have a transaction id")
1123+
}
1124+
beginRequests := requestsOfType(reqs, reflect.TypeOf(&sppb.BeginTransactionRequest{}))
1125+
if g, w := len(beginRequests), 2; g != w {
1126+
t.Fatalf("begin request count mismatch\n Got: %v\nWant: %v", g, w)
11081127
}
1109-
11101128
// Verify that the db is still usable.
11111129
if _, err := db.ExecContext(ctx, testutil.UpdateSingersSetLastName); err != nil {
11121130
t.Fatalf("failed to execute statement after transaction: %v", err)

auto_dml_batch_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,13 @@ func TestAutoBatchDml_FollowedByRollback(t *testing.T) {
303303
if g, w := len(commitRequests), 0; g != w {
304304
t.Fatalf("num commit requests mismatch\n Got: %v\nWant: %v", g, w)
305305
}
306+
beginRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.BeginTransactionRequest{}))
307+
if g, w := len(beginRequests), 0; g != w {
308+
t.Fatalf("num BeginTransaction requests mismatch\n Got: %v\nWant: %v", g, w)
309+
}
306310
rollbackRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.RollbackRequest{}))
307-
if g, w := len(rollbackRequests), 1; g != w {
311+
// There are no rollback requests sent to Spanner, as the transaction is never started.
312+
if g, w := len(rollbackRequests), 0; g != w {
308313
t.Fatalf("num rollback requests mismatch\n Got: %v\nWant: %v", g, w)
309314
}
310315
}

benchmarks/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ replace github.com/googleapis/go-sql-spanner => ../
88

99
require (
1010
cloud.google.com/go v0.121.3
11-
cloud.google.com/go/spanner v1.82.1-0.20250625132714-fe377af799f0
11+
cloud.google.com/go/spanner v1.83.0
1212
github.com/google/uuid v1.6.0
13-
github.com/googleapis/go-sql-spanner v1.14.0
13+
github.com/googleapis/go-sql-spanner v1.15.0
1414
google.golang.org/api v0.239.0
1515
google.golang.org/grpc v1.73.0
1616
google.golang.org/protobuf v1.36.6

benchmarks/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,8 +526,8 @@ cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+
526526
cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
527527
cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=
528528
cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=
529-
cloud.google.com/go/spanner v1.82.1-0.20250625132714-fe377af799f0 h1:JpZinZY/JMrWJAPoSVt08CwfCFkzMt2WWTjtlrnHc9Y=
530-
cloud.google.com/go/spanner v1.82.1-0.20250625132714-fe377af799f0/go.mod h1:QSWcjxszT0WRHNd8zyGI0WctrYA1N7j0yTFsWyol9Yw=
529+
cloud.google.com/go/spanner v1.83.0 h1:AH3QIoSIa01l3WbeTppkwCEYFNK1AER6drcYhPmwhxY=
530+
cloud.google.com/go/spanner v1.83.0/go.mod h1:QSWcjxszT0WRHNd8zyGI0WctrYA1N7j0yTFsWyol9Yw=
531531
cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
532532
cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
533533
cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=

conn.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ type conn struct {
256256
// transactions on this connection. This default is ignored if the BeginTx function is
257257
// called with an isolation level other than sql.LevelDefault.
258258
isolationLevel sql.IsolationLevel
259+
// beginTransactionOption determines the default transactions start mode.
260+
beginTransactionOption spanner.BeginTransactionOption
259261

260262
// execOptions are applied to the next statement or transaction that is executed
261263
// on this connection. It can also be set by passing it in as an argument to
@@ -690,6 +692,7 @@ func (c *conn) ResetSession(_ context.Context) error {
690692
c.autoBatchDmlUpdateCountVerification = !c.connector.connectorConfig.DisableAutoBatchDmlUpdateCountVerification
691693
c.retryAborts = c.connector.retryAbortsInternally
692694
c.isolationLevel = c.connector.connectorConfig.IsolationLevel
695+
c.beginTransactionOption = c.connector.connectorConfig.BeginTransactionOption
693696
// TODO: Reset the following fields to the connector default
694697
c.autocommitDMLMode = Transactional
695698
c.readOnlyStaleness = spanner.TimestampBound{}
@@ -953,7 +956,9 @@ func (c *conn) withTempTransactionOptions(options *ReadWriteTransactionOptions)
953956
func (c *conn) getTransactionOptions() ReadWriteTransactionOptions {
954957
if c.tempTransactionOptions != nil {
955958
defer func() { c.tempTransactionOptions = nil }()
956-
return *c.tempTransactionOptions
959+
opts := *c.tempTransactionOptions
960+
opts.TransactionOptions.BeginTransactionOption = c.convertDefaultBeginTransactionOption(opts.TransactionOptions.BeginTransactionOption)
961+
return opts
957962
}
958963
// Clear the transaction tag that has been set on the connection after returning
959964
// from this function.
@@ -973,6 +978,9 @@ func (c *conn) getTransactionOptions() ReadWriteTransactionOptions {
973978
txOpts.TransactionOptions.IsolationLevel = level
974979
}
975980
}
981+
if txOpts.TransactionOptions.BeginTransactionOption == spanner.DefaultBeginTransaction {
982+
txOpts.TransactionOptions.BeginTransactionOption = c.convertDefaultBeginTransactionOption(c.beginTransactionOption)
983+
}
976984
return txOpts
977985
}
978986

@@ -983,9 +991,11 @@ func (c *conn) withTempReadOnlyTransactionOptions(options *ReadOnlyTransactionOp
983991
func (c *conn) getReadOnlyTransactionOptions() ReadOnlyTransactionOptions {
984992
if c.tempReadOnlyTransactionOptions != nil {
985993
defer func() { c.tempReadOnlyTransactionOptions = nil }()
986-
return *c.tempReadOnlyTransactionOptions
994+
opts := *c.tempReadOnlyTransactionOptions
995+
opts.BeginTransactionOption = c.convertDefaultBeginTransactionOption(opts.BeginTransactionOption)
996+
return opts
987997
}
988-
return ReadOnlyTransactionOptions{TimestampBound: c.readOnlyStaleness}
998+
return ReadOnlyTransactionOptions{TimestampBound: c.readOnlyStaleness, BeginTransactionOption: c.convertDefaultBeginTransactionOption(c.beginTransactionOption)}
989999
}
9901000

9911001
func (c *conn) withTempBatchReadOnlyTransactionOptions(options *BatchReadOnlyTransactionOptions) {
@@ -1060,7 +1070,7 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
10601070
ro = &bo.ReadOnlyTransaction
10611071
} else {
10621072
logger = c.logger.With("tx", "ro")
1063-
ro = c.client.ReadOnlyTransaction().WithTimestampBound(readOnlyTxOpts.TimestampBound)
1073+
ro = c.client.ReadOnlyTransaction().WithBeginTransactionOption(readOnlyTxOpts.BeginTransactionOption).WithTimestampBound(readOnlyTxOpts.TimestampBound)
10641074
}
10651075
c.tx = &readOnlyTransaction{
10661076
roTx: ro,
@@ -1106,6 +1116,16 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
11061116
return c.tx, nil
11071117
}
11081118

1119+
func (c *conn) convertDefaultBeginTransactionOption(opt spanner.BeginTransactionOption) spanner.BeginTransactionOption {
1120+
if opt == spanner.DefaultBeginTransaction {
1121+
if c.beginTransactionOption == spanner.DefaultBeginTransaction {
1122+
return spanner.InlinedBeginTransaction
1123+
}
1124+
return c.beginTransactionOption
1125+
}
1126+
return opt
1127+
}
1128+
11091129
func (c *conn) inTransaction() bool {
11101130
return c.tx != nil
11111131
}

conn_with_mockserver_test.go

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,18 @@ func TestBeginTx(t *testing.T) {
4040

4141
requests := drainRequestsFromServer(server.TestSpanner)
4242
beginRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.BeginTransactionRequest{}))
43-
if g, w := len(beginRequests), 1; g != w {
43+
if g, w := len(beginRequests), 0; g != w {
4444
t.Fatalf("begin requests count mismatch\n Got: %v\nWant: %v", g, w)
4545
}
46-
request := beginRequests[0].(*spannerpb.BeginTransactionRequest)
47-
if g, w := request.Options.GetIsolationLevel(), spannerpb.TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED; g != w {
46+
executeRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.ExecuteSqlRequest{}))
47+
if g, w := len(executeRequests), 1; g != w {
48+
t.Fatalf("execute requests count mismatch\n Got: %v\nWant: %v", g, w)
49+
}
50+
request := executeRequests[0].(*spannerpb.ExecuteSqlRequest)
51+
if request.GetTransaction() == nil || request.GetTransaction().GetBegin() == nil {
52+
t.Fatal("missing begin transaction on ExecuteSqlRequest")
53+
}
54+
if g, w := request.GetTransaction().GetBegin().GetIsolationLevel(), spannerpb.TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED; g != w {
4855
t.Fatalf("begin isolation level mismatch\n Got: %v\nWant: %v", g, w)
4956
}
5057
}
@@ -68,6 +75,53 @@ func TestTwoTransactionsOnOneConn(t *testing.T) {
6875
}
6976
}
7077

78+
func TestExplicitBeginTx(t *testing.T) {
79+
t.Parallel()
80+
81+
db, server, teardown := setupTestDBConnectionWithConnectorConfig(t, ConnectorConfig{
82+
Project: "p",
83+
Instance: "i",
84+
Database: "d",
85+
86+
BeginTransactionOption: spanner.ExplicitBeginTransaction,
87+
})
88+
defer teardown()
89+
ctx := context.Background()
90+
91+
for _, readOnly := range []bool{true, false} {
92+
tx, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: readOnly})
93+
if err != nil {
94+
t.Fatal(err)
95+
}
96+
res, err := tx.QueryContext(ctx, testutil.SelectFooFromBar)
97+
if err != nil {
98+
t.Fatal(err)
99+
}
100+
for res.Next() {
101+
}
102+
if err := res.Err(); err != nil {
103+
t.Fatal(err)
104+
}
105+
if err := tx.Rollback(); err != nil {
106+
t.Fatal(err)
107+
}
108+
109+
requests := drainRequestsFromServer(server.TestSpanner)
110+
beginRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.BeginTransactionRequest{}))
111+
if g, w := len(beginRequests), 1; g != w {
112+
t.Fatalf("begin requests count mismatch\n Got: %v\nWant: %v", g, w)
113+
}
114+
executeRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.ExecuteSqlRequest{}))
115+
if g, w := len(executeRequests), 1; g != w {
116+
t.Fatalf("execute requests count mismatch\n Got: %v\nWant: %v", g, w)
117+
}
118+
request := executeRequests[0].(*spannerpb.ExecuteSqlRequest)
119+
if request.GetTransaction() == nil || request.GetTransaction().GetId() == nil {
120+
t.Fatal("missing transaction id on ExecuteSqlRequest")
121+
}
122+
}
123+
}
124+
71125
func TestBeginTxWithIsolationLevel(t *testing.T) {
72126
t.Parallel()
73127

@@ -96,12 +150,19 @@ func TestBeginTxWithIsolationLevel(t *testing.T) {
96150

97151
requests := drainRequestsFromServer(server.TestSpanner)
98152
beginRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.BeginTransactionRequest{}))
99-
if g, w := len(beginRequests), 1; g != w {
153+
if g, w := len(beginRequests), 0; g != w {
100154
t.Fatalf("begin requests count mismatch\n Got: %v\nWant: %v", g, w)
101155
}
102-
request := beginRequests[0].(*spannerpb.BeginTransactionRequest)
156+
executeRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.ExecuteSqlRequest{}))
157+
if g, w := len(executeRequests), 1; g != w {
158+
t.Fatalf("execute requests count mismatch\n Got: %v\nWant: %v", g, w)
159+
}
160+
request := executeRequests[0].(*spannerpb.ExecuteSqlRequest)
161+
if request.GetTransaction() == nil || request.GetTransaction().GetBegin() == nil {
162+
t.Fatalf("execute request does not have a begin transaction")
163+
}
103164
wantIsolationLevel, _ := toProtoIsolationLevel(originalLevel)
104-
if g, w := request.Options.GetIsolationLevel(), wantIsolationLevel; g != w {
165+
if g, w := request.GetTransaction().GetBegin().GetIsolationLevel(), wantIsolationLevel; g != w {
105166
t.Fatalf("begin isolation level mismatch\n Got: %v\nWant: %v", g, w)
106167
}
107168
}
@@ -182,12 +243,19 @@ func TestDefaultIsolationLevel(t *testing.T) {
182243

183244
requests := drainRequestsFromServer(server.TestSpanner)
184245
beginRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.BeginTransactionRequest{}))
185-
if g, w := len(beginRequests), 1; g != w {
246+
if g, w := len(beginRequests), 0; g != w {
186247
t.Fatalf("begin requests count mismatch\n Got: %v\nWant: %v", g, w)
187248
}
188-
request := beginRequests[0].(*spannerpb.BeginTransactionRequest)
249+
executeRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.ExecuteSqlRequest{}))
250+
if g, w := len(executeRequests), 1; g != w {
251+
t.Fatalf("execute requests count mismatch\n Got: %v\nWant: %v", g, w)
252+
}
253+
request := executeRequests[0].(*spannerpb.ExecuteSqlRequest)
254+
if request.GetTransaction() == nil || request.GetTransaction().GetBegin() == nil {
255+
t.Fatalf("ExecuteSqlRequest should have a Begin transaction")
256+
}
189257
wantIsolationLevel, _ := toProtoIsolationLevel(originalLevel)
190-
if g, w := request.Options.GetIsolationLevel(), wantIsolationLevel; g != w {
258+
if g, w := request.GetTransaction().GetBegin().GetIsolationLevel(), wantIsolationLevel; g != w {
191259
t.Fatalf("begin isolation level mismatch\n Got: %v\nWant: %v", g, w)
192260
}
193261
}
@@ -225,7 +293,7 @@ func TestSetIsolationLevel(t *testing.T) {
225293
if g, w := level, sql.LevelSnapshot; g != w {
226294
t.Fatalf("isolation level mismatch\n Got: %v\nWant: %v", g, w)
227295
}
228-
conn.Close()
296+
_ = conn.Close()
229297
}
230298
}
231299

@@ -287,7 +355,7 @@ func TestDDLUsingQueryContextInReadOnlyTx(t *testing.T) {
287355
if err != nil {
288356
t.Fatal(err)
289357
}
290-
defer tx.Rollback()
358+
defer func() { _ = tx.Rollback() }()
291359

292360
// DDL statements should not use the query context in a read-only transaction.
293361
_, err = tx.QueryContext(ctx, "CREATE TABLE Foo (Bar STRING(100))")
@@ -310,7 +378,7 @@ func TestDDLUsingQueryContextInReadWriteTransaction(t *testing.T) {
310378
if err != nil {
311379
t.Fatal(err)
312380
}
313-
defer tx.Rollback()
381+
defer func() { _ = tx.Rollback() }()
314382

315383
// DDL statements should not use the query context in a read-write transaction.
316384
_, err = tx.QueryContext(ctx, "CREATE TABLE Foo (Bar STRING(100))")

connection_leak_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func simpleQuery(ctx context.Context, t *testing.T, db *sql.DB) {
7575
if err != nil {
7676
t.Fatal(err)
7777
}
78-
defer rows.Close()
78+
defer silentClose(rows)
7979

8080
for want := int64(1); rows.Next(); want++ {
8181
_, err := rows.Columns()
@@ -202,7 +202,7 @@ func readOnlyTxWithStaleness(ctx context.Context, t *testing.T, db *sql.DB) {
202202
if err != nil {
203203
t.Fatal(err)
204204
}
205-
defer conn.Close()
205+
defer silentClose(conn)
206206
if _, err := conn.ExecContext(ctx, "SET READ_ONLY_STALENESS = 'EXACT_STALENESS 10s'"); err != nil {
207207
t.Fatalf("Set read-only staleness: %v", err)
208208
}
@@ -233,7 +233,7 @@ func simpleReadWriteTx(ctx context.Context, t *testing.T, db *sql.DB) {
233233
if err != nil {
234234
t.Fatal(err)
235235
}
236-
defer conn.Close()
236+
defer silentClose(conn)
237237
if _, err := conn.ExecContext(ctx, "set max_commit_delay='10ms'"); err != nil {
238238
t.Fatal(err)
239239
}

0 commit comments

Comments
 (0)