Skip to content

Commit 281be01

Browse files
impl(spanner): add support for Read Lock Mode in RW transactions (#15235)
* feat(spanner): introduce concurrency control options * impl(spanner): introduce concurrency control options This adds support for Transaction_ReadWriteOptions_ReadLockMode by adding a constructor of ReadWriteOptions with a special enum, whose values can be "optimistic", "pessimistic" and "unspecified". * test: add integration test * chore: format files * chore: fix typo * chore: format comments * chore: use switch to map ReadLockMode enum proto * chore: cleanup * chore: use explicit constructor modifier * chore: refresh transaction id in it * chore: linter fixes * chore: restore format * chore: linter ii * chore: remove unnecessary comments
1 parent 4ea3d12 commit 281be01

File tree

4 files changed

+112
-2
lines changed

4 files changed

+112
-2
lines changed

google/cloud/spanner/integration_tests/client_integration_test.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "google/cloud/spanner/client.h"
1717
#include "google/cloud/spanner/database.h"
1818
#include "google/cloud/spanner/mutations.h"
19+
#include "google/cloud/spanner/options.h"
1920
#include "google/cloud/spanner/testing/database_integration_test.h"
2021
#include "google/cloud/credentials.h"
2122
#include "google/cloud/internal/getenv.h"
@@ -787,6 +788,34 @@ void CheckExecuteQueryWithSingleUseOptions(
787788
EXPECT_THAT(actual_rows, UnorderedElementsAreArray(expected_rows));
788789
}
789790

791+
TEST_F(ClientIntegrationTest, ReadLockModeOptionIsSent) {
792+
auto const singer_id = 101;
793+
auto mutation_helper = [singer_id](std::string const& new_name) {
794+
return Mutations{MakeInsertOrUpdateMutation(
795+
"Singers", {"SingerId", "FirstName"}, singer_id, new_name)};
796+
};
797+
798+
// Initial insert
799+
auto insert = client_->Commit(mutation_helper("InitialName"));
800+
ASSERT_STATUS_OK(insert);
801+
802+
auto read_lock_mode = Transaction::ReadLockMode::kOptimistic;
803+
auto tx_a =
804+
MakeReadWriteTransaction(Transaction::ReadWriteOptions(read_lock_mode));
805+
auto tx_a_read_result = client_->Read(
806+
tx_a, "Singers", KeySet().AddKey(MakeKey(singer_id)), {"SingerId"});
807+
for (auto const& row : StreamOf<std::tuple<std::int64_t>>(tx_a_read_result)) {
808+
EXPECT_STATUS_OK(row);
809+
}
810+
tx_a = MakeReadWriteTransaction(
811+
tx_a, Transaction::ReadWriteOptions(read_lock_mode));
812+
813+
auto optimistic_result =
814+
client_->Commit(tx_a, mutation_helper("SecondModifiedName"));
815+
816+
EXPECT_STATUS_OK(optimistic_result);
817+
}
818+
790819
/// @test Test ExecuteQuery() with bounded staleness set by a timestamp.
791820
TEST_F(ClientIntegrationTest, ExecuteQueryBoundedStalenessTimestamp) {
792821
CheckExecuteQueryWithSingleUseOptions(

google/cloud/spanner/transaction.cc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@ google::protobuf::Duration ToProto(std::chrono::nanoseconds ns) {
3434
return proto;
3535
}
3636

37+
google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode
38+
ProtoReadLockMode(
39+
absl::optional<Transaction::ReadLockMode> const& read_lock_mode) {
40+
if (!read_lock_mode) {
41+
return google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode::
42+
TransactionOptions_ReadWrite_ReadLockMode_READ_LOCK_MODE_UNSPECIFIED;
43+
}
44+
switch (*read_lock_mode) {
45+
case Transaction::ReadLockMode::kOptimistic:
46+
return google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode::
47+
TransactionOptions_ReadWrite_ReadLockMode_OPTIMISTIC;
48+
case Transaction::ReadLockMode::kPessimistic:
49+
return google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode::
50+
TransactionOptions_ReadWrite_ReadLockMode_PESSIMISTIC;
51+
default:
52+
return google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode::
53+
TransactionOptions_ReadWrite_ReadLockMode_READ_LOCK_MODE_UNSPECIFIED;
54+
}
55+
}
56+
3757
google::spanner::v1::TransactionOptions MakeOpts(
3858
google::spanner::v1::TransactionOptions_ReadOnly ro_opts) {
3959
google::spanner::v1::TransactionOptions opts;
@@ -71,7 +91,11 @@ Transaction::ReadOnlyOptions::ReadOnlyOptions(
7191
ro_opts_.set_return_read_timestamp(true);
7292
}
7393

74-
Transaction::ReadWriteOptions::ReadWriteOptions() = default; // currently none
94+
Transaction::ReadWriteOptions::ReadWriteOptions() = default;
95+
96+
Transaction::ReadWriteOptions::ReadWriteOptions(ReadLockMode read_lock_mode) {
97+
rw_opts_.set_read_lock_mode(ProtoReadLockMode(read_lock_mode));
98+
}
7599

76100
Transaction::ReadWriteOptions& Transaction::ReadWriteOptions::WithTag(
77101
absl::optional<std::string> tag) {

google/cloud/spanner/transaction.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,28 @@ class Transaction {
7878
google::spanner::v1::TransactionOptions_ReadOnly ro_opts_;
7979
};
8080

81+
/**
82+
* Read lock mode for ReadWrite transactions
83+
* The Spanner V1 Transaction proto classes have their own enum
84+
* implementations.
85+
* See google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode
86+
* This is a shorthand convenience for the developer.
87+
*/
88+
enum class ReadLockMode {
89+
kUnspecified,
90+
kPessimistic,
91+
kOptimistic,
92+
};
93+
8194
/**
8295
* Options for ReadWrite transactions.
8396
*/
8497
class ReadWriteOptions {
8598
public:
86-
// There are currently no read-write options.
8799
ReadWriteOptions();
88100

101+
explicit ReadWriteOptions(ReadLockMode read_lock_mode);
102+
89103
// A tag used for collecting statistics about the transaction.
90104
ReadWriteOptions& WithTag(absl::optional<std::string> tag);
91105

google/cloud/spanner/transaction_test.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ TEST(TransactionOptions, Construction) {
3333
Transaction::ReadOnlyOptions exact_dur(staleness);
3434

3535
Transaction::ReadWriteOptions none;
36+
Transaction::ReadWriteOptions rw_with_read_lock(
37+
Transaction::ReadLockMode::kOptimistic);
3638

3739
Transaction::SingleUseOptions su_strong(strong);
3840
Transaction::SingleUseOptions su_exact_ts(exact_ts);
@@ -167,6 +169,47 @@ TEST(Transaction, MultiplexedPreviousTransactionId) {
167169
});
168170
}
169171

172+
TEST(Transaction, ReadWriteOptionsWithTag) {
173+
auto opts = Transaction::ReadWriteOptions().WithTag("test-tag");
174+
Transaction txn = MakeReadWriteTransaction(opts);
175+
spanner_internal::Visit(
176+
txn, [&](spanner_internal::SessionHolder& /*session*/,
177+
StatusOr<google::spanner::v1::TransactionSelector>& s,
178+
spanner_internal::TransactionContext const& ctx) {
179+
EXPECT_TRUE(s->has_begin());
180+
EXPECT_TRUE(s->begin().has_read_write());
181+
EXPECT_EQ(ctx.tag, "test-tag");
182+
return 0;
183+
});
184+
}
185+
186+
TEST(Transaction, ReadWriteOptionsWithReadLockMode) {
187+
auto check_lock_mode =
188+
[](Transaction::ReadLockMode mode,
189+
google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode
190+
expected_proto_mode) {
191+
auto opts = Transaction::ReadWriteOptions(mode);
192+
Transaction txn = MakeReadWriteTransaction(opts);
193+
spanner_internal::Visit(
194+
txn, [&](spanner_internal::SessionHolder& /*session*/,
195+
StatusOr<google::spanner::v1::TransactionSelector>& s,
196+
spanner_internal::TransactionContext const& /*ctx*/) {
197+
EXPECT_TRUE(s->has_begin());
198+
EXPECT_TRUE(s->begin().has_read_write());
199+
EXPECT_EQ(s->begin().read_write().read_lock_mode(),
200+
expected_proto_mode);
201+
return 0;
202+
});
203+
};
204+
205+
check_lock_mode(
206+
Transaction::ReadLockMode::kPessimistic,
207+
google::spanner::v1::TransactionOptions_ReadWrite::PESSIMISTIC);
208+
check_lock_mode(
209+
Transaction::ReadLockMode::kOptimistic,
210+
google::spanner::v1::TransactionOptions_ReadWrite::OPTIMISTIC);
211+
}
212+
170213
} // namespace
171214
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
172215
} // namespace spanner

0 commit comments

Comments
 (0)