Skip to content

Commit aa026f9

Browse files
authored
feat(spanner): set read lock mode at client level (#16068)
1 parent 3bb407d commit aa026f9

File tree

4 files changed

+135
-0
lines changed

4 files changed

+135
-0
lines changed

google/cloud/spanner/options.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,15 @@ struct TransactionIsolationLevelOption {
430430
using Type = spanner::Transaction::IsolationLevel;
431431
};
432432

433+
/**
434+
* Option for `google::cloud::Options` to set the transaction read lock mode.
435+
*
436+
* @ingroup google-cloud-spanner-options
437+
*/
438+
struct TransactionReadLockModeOption {
439+
using Type = spanner::Transaction::ReadLockMode;
440+
};
441+
433442
/**
434443
* Option for `google::cloud::Options` to return additional statistics
435444
* about the committed transaction in a `spanner::CommitResult`.

google/cloud/spanner/samples/samples.cc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3539,6 +3539,71 @@ void IsolationLevelSettingCommand(std::vector<std::string> argv) {
35393539
IsolationLevelSetting(argv[0], argv[1], argv[2]);
35403540
}
35413541

3542+
//! [START spanner_read_lock_mode]
3543+
void ReadLockModeSetting(std::string const& project_id,
3544+
std::string const& instance_id,
3545+
std::string const& database_id) {
3546+
namespace spanner = ::google::cloud::spanner;
3547+
using ::google::cloud::Options;
3548+
using ::google::cloud::StatusOr;
3549+
3550+
auto db = spanner::Database(project_id, instance_id, database_id);
3551+
3552+
// The read lock mode specified at the client-level will be applied
3553+
// to all RW transactions.
3554+
auto options = Options{}.set<spanner::TransactionReadLockModeOption>(
3555+
spanner::Transaction::ReadLockMode::kOptimistic);
3556+
auto client = spanner::Client(spanner::MakeConnection(db, options));
3557+
3558+
auto commit = client.Commit(
3559+
[&client](
3560+
spanner::Transaction const& txn) -> StatusOr<spanner::Mutations> {
3561+
// Read an AlbumTitle.
3562+
auto sql = spanner::SqlStatement(
3563+
"SELECT AlbumTitle from Albums WHERE SingerId = @SingerId and "
3564+
"AlbumId = @AlbumId",
3565+
{{"SingerId", spanner::Value(2)}, {"AlbumId", spanner::Value(1)}});
3566+
auto rows = client.ExecuteQuery(txn, std::move(sql));
3567+
for (auto const& row :
3568+
spanner::StreamOf<std::tuple<std::string>>(rows)) {
3569+
if (!row) return row.status();
3570+
std::cout << "Current Album Title: " << std::get<0>(*row) << "\n";
3571+
}
3572+
3573+
// Update the title.
3574+
auto update_sql = spanner::SqlStatement(
3575+
"UPDATE Albums "
3576+
"SET AlbumTitle = @AlbumTitle "
3577+
"WHERE SingerId = @SingerId and AlbumId = @AlbumId",
3578+
{{"AlbumTitle", spanner::Value("A New Title")},
3579+
{"SingerId", spanner::Value(2)},
3580+
{"AlbumId", spanner::Value(1)}});
3581+
auto result = client.ExecuteDml(txn, std::move(update_sql));
3582+
if (!result) return result.status();
3583+
std::cout << result->RowsModified() << " record(s) updated.\n";
3584+
3585+
return spanner::Mutations{};
3586+
},
3587+
// The read lock mode specified at the transaction-level takes
3588+
// precedence over the read lock mode configured at the client-level.
3589+
// kPessimistic is used here to demonstrate overriding the client-level
3590+
// setting.
3591+
Options{}.set<spanner::TransactionReadLockModeOption>(
3592+
spanner::Transaction::ReadLockMode::kPessimistic));
3593+
3594+
if (!commit) throw std::move(commit).status();
3595+
std::cout << "Update was successful [spanner_read_lock_mode]\n";
3596+
}
3597+
//! [END spanner_read_lock_mode]
3598+
3599+
void ReadLockModeSettingCommand(std::vector<std::string> argv) {
3600+
if (argv.size() != 3) {
3601+
throw std::runtime_error(
3602+
"read-lock-mode-setting <project-id> <instance-id> <database-id>");
3603+
}
3604+
ReadLockModeSetting(argv[0], argv[1], argv[2]);
3605+
}
3606+
35423607
//! [START spanner_get_commit_stats]
35433608
void GetCommitStatistics(google::cloud::spanner::Client client) {
35443609
namespace spanner = ::google::cloud::spanner;
@@ -5258,6 +5323,7 @@ int RunOneCommand(std::vector<std::string> argv) {
52585323
ReadDataWithStoringIndex),
52595324
make_command_entry("read-write-transaction", ReadWriteTransaction),
52605325
{"isolation-level-setting", IsolationLevelSettingCommand},
5326+
{"read-lock-mode-setting", ReadLockModeSettingCommand},
52615327
make_command_entry("get-commit-stats", GetCommitStatistics),
52625328
make_command_entry("dml-standard-insert", DmlStandardInsert),
52635329
make_command_entry("dml-standard-update", DmlStandardUpdate),

google/cloud/spanner/transaction.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ google::spanner::v1::TransactionOptions MakeOpts(
9393
ProtoIsolationLevel(current.get<TransactionIsolationLevelOption>()));
9494
}
9595

96+
if (opts.read_write().read_lock_mode() ==
97+
google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode::
98+
TransactionOptions_ReadWrite_ReadLockMode_READ_LOCK_MODE_UNSPECIFIED &&
99+
current.has<TransactionReadLockModeOption>()) {
100+
opts.mutable_read_write()->set_read_lock_mode(
101+
ProtoReadLockMode(current.get<TransactionReadLockModeOption>()));
102+
}
103+
96104
return opts;
97105
}
98106

google/cloud/spanner/transaction_test.cc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,58 @@ TEST(Transaction, IsolationLevelNotSpecified) {
217217
});
218218
}
219219

220+
TEST(Transaction, ReadLockModePrecedence) {
221+
internal::OptionsSpan span(Options{}.set<TransactionReadLockModeOption>(
222+
Transaction::ReadLockMode::kOptimistic));
223+
224+
// Case 1: Per-call overrides default options
225+
auto opts =
226+
Transaction::ReadWriteOptions(Transaction::ReadLockMode::kPessimistic);
227+
Transaction txn = MakeReadWriteTransaction(opts);
228+
spanner_internal::Visit(
229+
txn, [](spanner_internal::SessionHolder&,
230+
StatusOr<google::spanner::v1::TransactionSelector>& s,
231+
spanner_internal::TransactionContext const&) {
232+
EXPECT_EQ(
233+
s->begin().read_write().read_lock_mode(),
234+
google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode::
235+
TransactionOptions_ReadWrite_ReadLockMode_PESSIMISTIC);
236+
return 0;
237+
});
238+
239+
// Case 2: Fallback to default options
240+
auto opts_default = Transaction::ReadWriteOptions();
241+
Transaction txn_default = MakeReadWriteTransaction(opts_default);
242+
spanner_internal::Visit(
243+
txn_default, [](spanner_internal::SessionHolder&,
244+
StatusOr<google::spanner::v1::TransactionSelector>& s,
245+
spanner_internal::TransactionContext const&) {
246+
EXPECT_EQ(
247+
s->begin().read_write().read_lock_mode(),
248+
google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode::
249+
TransactionOptions_ReadWrite_ReadLockMode_OPTIMISTIC);
250+
return 0;
251+
});
252+
}
253+
254+
TEST(Transaction, ReadLockModeNotSpecified) {
255+
// Case: Read lock mode not specified in transaction options or default
256+
// options
257+
auto opts = Transaction::ReadWriteOptions();
258+
Transaction txn = MakeReadWriteTransaction(opts);
259+
spanner_internal::Visit(txn, [](spanner_internal::SessionHolder&,
260+
StatusOr<
261+
google::spanner::v1::TransactionSelector>&
262+
s,
263+
spanner_internal::TransactionContext const&) {
264+
EXPECT_EQ(
265+
s->begin().read_write().read_lock_mode(),
266+
google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode::
267+
TransactionOptions_ReadWrite_ReadLockMode_READ_LOCK_MODE_UNSPECIFIED);
268+
return 0;
269+
});
270+
}
271+
220272
TEST(Transaction, ReadWriteOptionsWithTag) {
221273
auto opts = Transaction::ReadWriteOptions().WithTag("test-tag");
222274
Transaction txn = MakeReadWriteTransaction(opts);

0 commit comments

Comments
 (0)