Skip to content

Commit 48ba16f

Browse files
authored
feat(spanner): add support for Optimistic Concurrency Control (#7332)
1 parent cf1332d commit 48ba16f

File tree

2 files changed

+103
-4
lines changed

2 files changed

+103
-4
lines changed

spanner/client_test.go

+88
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,88 @@ func TestClient_ReadWriteTransactionWithOptions(t *testing.T) {
10361036
}
10371037
}
10381038

1039+
func TestClient_ReadWriteTransactionWithOptimisticLockMode_ExecuteSqlRequest(t *testing.T) {
1040+
server, client, teardown := setupMockedTestServer(t)
1041+
defer teardown()
1042+
ctx := context.Background()
1043+
server.TestSpanner.PutExecutionTime(MethodExecuteStreamingSql,
1044+
SimulatedExecutionTime{
1045+
Errors: []error{status.Error(codes.Unavailable, "Temporary unavailable"), status.Error(codes.Aborted, "Transaction aborted")},
1046+
})
1047+
_, err := client.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, tx *ReadWriteTransaction) error {
1048+
iter := tx.Query(ctx, NewStatement(SelectSingerIDAlbumIDAlbumTitleFromAlbums))
1049+
defer iter.Stop()
1050+
_, err := iter.Next()
1051+
return err
1052+
}, TransactionOptions{ReadLockMode: sppb.TransactionOptions_ReadWrite_OPTIMISTIC})
1053+
if err != nil {
1054+
t.Fatalf("Failed to execute the transaction: %s", err)
1055+
}
1056+
requests := drainRequestsFromServer(server.TestSpanner)
1057+
if err := compareRequests([]interface{}{
1058+
&sppb.BatchCreateSessionsRequest{},
1059+
&sppb.ExecuteSqlRequest{},
1060+
&sppb.ExecuteSqlRequest{},
1061+
&sppb.BeginTransactionRequest{},
1062+
&sppb.ExecuteSqlRequest{},
1063+
&sppb.CommitRequest{}}, requests); err != nil {
1064+
t.Fatal(err)
1065+
}
1066+
if requests[1].(*sppb.ExecuteSqlRequest).GetTransaction().GetBegin().GetReadWrite().GetReadLockMode() != sppb.TransactionOptions_ReadWrite_OPTIMISTIC {
1067+
t.Fatal("Transaction is not set to optimistic")
1068+
}
1069+
if requests[2].(*sppb.ExecuteSqlRequest).GetTransaction().GetBegin().GetReadWrite().GetReadLockMode() != sppb.TransactionOptions_ReadWrite_OPTIMISTIC {
1070+
t.Fatal("Transaction is not set to optimistic")
1071+
}
1072+
if requests[3].(*sppb.BeginTransactionRequest).GetOptions().GetReadWrite().GetReadLockMode() != sppb.TransactionOptions_ReadWrite_OPTIMISTIC {
1073+
t.Fatal("Begin Transaction is not set to optimistic")
1074+
}
1075+
if _, ok := requests[4].(*sppb.ExecuteSqlRequest).Transaction.GetSelector().(*sppb.TransactionSelector_Id); !ok {
1076+
t.Fatal("expected streaming query to use transactionID from explicit begin transaction")
1077+
}
1078+
}
1079+
1080+
func TestClient_ReadWriteTransactionWithOptimisticLockMode_ReadRequest(t *testing.T) {
1081+
server, client, teardown := setupMockedTestServer(t)
1082+
defer teardown()
1083+
ctx := context.Background()
1084+
server.TestSpanner.PutExecutionTime(MethodStreamingRead,
1085+
SimulatedExecutionTime{
1086+
Errors: []error{status.Error(codes.Unavailable, "Temporary unavailable"), status.Error(codes.Aborted, "Transaction aborted")},
1087+
})
1088+
_, err := client.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, tx *ReadWriteTransaction) error {
1089+
iter := tx.Read(ctx, "Albums", KeySets(Key{"foo"}), []string{"SingerId", "AlbumId", "AlbumTitle"})
1090+
defer iter.Stop()
1091+
_, err := iter.Next()
1092+
return err
1093+
}, TransactionOptions{ReadLockMode: sppb.TransactionOptions_ReadWrite_OPTIMISTIC})
1094+
if err != nil {
1095+
t.Fatalf("Failed to execute the transaction: %s", err)
1096+
}
1097+
requests := drainRequestsFromServer(server.TestSpanner)
1098+
if err := compareRequests([]interface{}{
1099+
&sppb.BatchCreateSessionsRequest{},
1100+
&sppb.ReadRequest{},
1101+
&sppb.ReadRequest{},
1102+
&sppb.BeginTransactionRequest{},
1103+
&sppb.ReadRequest{},
1104+
&sppb.CommitRequest{}}, requests); err != nil {
1105+
t.Fatal(err)
1106+
}
1107+
if requests[1].(*sppb.ReadRequest).GetTransaction().GetBegin().GetReadWrite().GetReadLockMode() != sppb.TransactionOptions_ReadWrite_OPTIMISTIC {
1108+
t.Fatal("Transaction is not set to optimistic")
1109+
}
1110+
if requests[2].(*sppb.ReadRequest).GetTransaction().GetBegin().GetReadWrite().GetReadLockMode() != sppb.TransactionOptions_ReadWrite_OPTIMISTIC {
1111+
t.Fatal("Transaction is not set to optimistic")
1112+
}
1113+
if requests[3].(*sppb.BeginTransactionRequest).GetOptions().GetReadWrite().GetReadLockMode() != sppb.TransactionOptions_ReadWrite_OPTIMISTIC {
1114+
t.Fatal("Begin Transaction is not set to optimistic")
1115+
}
1116+
if _, ok := requests[4].(*sppb.ReadRequest).Transaction.GetSelector().(*sppb.TransactionSelector_Id); !ok {
1117+
t.Fatal("expected streaming read to use transactionID from explicit begin transaction")
1118+
}
1119+
}
1120+
10391121
func TestClient_ReadWriteStmtBasedTransaction_TransactionOptions(t *testing.T) {
10401122
for _, tt := range transactionOptionsTestCases() {
10411123
t.Run(tt.name, func(t *testing.T) {
@@ -2999,6 +3081,12 @@ func transactionOptionsTestCases() []TransactionOptionsTestCase {
29993081
write: &TransactionOptions{CommitOptions: CommitOptions{ReturnCommitStats: true}, TransactionTag: "writeTransactionTag", CommitPriority: sppb.RequestOptions_PRIORITY_MEDIUM},
30003082
want: &TransactionOptions{CommitOptions: CommitOptions{ReturnCommitStats: true}, TransactionTag: "writeTransactionTag", CommitPriority: sppb.RequestOptions_PRIORITY_MEDIUM},
30013083
},
3084+
{
3085+
name: "Read lock mode is optimistic",
3086+
client: &TransactionOptions{ReadLockMode: sppb.TransactionOptions_ReadWrite_OPTIMISTIC},
3087+
write: &TransactionOptions{},
3088+
want: &TransactionOptions{},
3089+
},
30023090
}
30033091
}
30043092

spanner/transaction.go

+15-4
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ type TransactionOptions struct {
103103
// CommitPriority is the priority to use for the Commit RPC for the
104104
// transaction.
105105
CommitPriority sppb.RequestOptions_Priority
106+
107+
// the transaction lock mode is used to specify a concurrency mode for the
108+
// read/query operations. It works for a read/write transaction only.
109+
ReadLockMode sppb.TransactionOptions_ReadWrite_ReadLockMode
106110
}
107111

108112
// merge combines two TransactionOptions that the input parameter will have higher
@@ -119,6 +123,9 @@ func (to TransactionOptions) merge(opts TransactionOptions) TransactionOptions {
119123
if opts.CommitPriority != sppb.RequestOptions_PRIORITY_UNSPECIFIED {
120124
merged.CommitPriority = opts.CommitPriority
121125
}
126+
if opts.ReadLockMode != sppb.TransactionOptions_ReadWrite_READ_LOCK_MODE_UNSPECIFIED {
127+
merged.ReadLockMode = opts.ReadLockMode
128+
}
122129
return merged
123130
}
124131

@@ -1244,7 +1251,9 @@ func (t *ReadWriteTransaction) getTransactionSelector() *sppb.TransactionSelecto
12441251
Selector: &sppb.TransactionSelector_Begin{
12451252
Begin: &sppb.TransactionOptions{
12461253
Mode: &sppb.TransactionOptions_ReadWrite_{
1247-
ReadWrite: &sppb.TransactionOptions_ReadWrite{},
1254+
ReadWrite: &sppb.TransactionOptions_ReadWrite{
1255+
ReadLockMode: t.txOpts.ReadLockMode,
1256+
},
12481257
},
12491258
},
12501259
},
@@ -1278,12 +1287,14 @@ func (t *ReadWriteTransaction) release(err error) {
12781287
}
12791288
}
12801289

1281-
func beginTransaction(ctx context.Context, sid string, client *vkit.Client) (transactionID, error) {
1290+
func beginTransaction(ctx context.Context, sid string, client *vkit.Client, opts TransactionOptions) (transactionID, error) {
12821291
res, err := client.BeginTransaction(ctx, &sppb.BeginTransactionRequest{
12831292
Session: sid,
12841293
Options: &sppb.TransactionOptions{
12851294
Mode: &sppb.TransactionOptions_ReadWrite_{
1286-
ReadWrite: &sppb.TransactionOptions_ReadWrite{},
1295+
ReadWrite: &sppb.TransactionOptions_ReadWrite{
1296+
ReadLockMode: opts.ReadLockMode,
1297+
},
12871298
},
12881299
},
12891300
})
@@ -1344,7 +1355,7 @@ func (t *ReadWriteTransaction) begin(ctx context.Context) error {
13441355
return err
13451356
}
13461357
}
1347-
tx, err = beginTransaction(contextWithOutgoingMetadata(ctx, sh.getMetadata()), sh.getID(), sh.getClient())
1358+
tx, err = beginTransaction(contextWithOutgoingMetadata(ctx, sh.getMetadata()), sh.getID(), sh.getClient(), t.txOpts)
13481359
if isSessionNotFoundError(err) {
13491360
sh.destroy()
13501361
continue

0 commit comments

Comments
 (0)