From f65db3f5b53d416e04f6b681a29a3f932dbab150 Mon Sep 17 00:00:00 2001 From: skuruppu Date: Thu, 19 Feb 2026 11:40:07 +0000 Subject: [PATCH] feat(spanner): set read lock mode at client level The read lock mode can be set at the client level, which is then set as the read lock mode in all RW transactions. However, the read lock mode can be overridden at a transaction level by explicitly specifying the read lock mode in the transaction options. Also adds a sample for setting the read lock mode at the client level and overriding it at the transaction level. --- google/cloud/spanner/options.h | 9 ++++ google/cloud/spanner/samples/samples.cc | 66 ++++++++++++++++++++++++ google/cloud/spanner/transaction.cc | 8 +++ google/cloud/spanner/transaction_test.cc | 52 +++++++++++++++++++ 4 files changed, 135 insertions(+) diff --git a/google/cloud/spanner/options.h b/google/cloud/spanner/options.h index 71ff33fcc68b0..f749c73914da3 100644 --- a/google/cloud/spanner/options.h +++ b/google/cloud/spanner/options.h @@ -430,6 +430,15 @@ struct TransactionIsolationLevelOption { using Type = spanner::Transaction::IsolationLevel; }; +/** + * Option for `google::cloud::Options` to set the transaction read lock mode. + * + * @ingroup google-cloud-spanner-options + */ +struct TransactionReadLockModeOption { + using Type = spanner::Transaction::ReadLockMode; +}; + /** * Option for `google::cloud::Options` to return additional statistics * about the committed transaction in a `spanner::CommitResult`. diff --git a/google/cloud/spanner/samples/samples.cc b/google/cloud/spanner/samples/samples.cc index 50fedd8ec1f0c..e3abff8e3cc04 100644 --- a/google/cloud/spanner/samples/samples.cc +++ b/google/cloud/spanner/samples/samples.cc @@ -3539,6 +3539,71 @@ void IsolationLevelSettingCommand(std::vector argv) { IsolationLevelSetting(argv[0], argv[1], argv[2]); } +//! [START spanner_read_lock_mode] +void ReadLockModeSetting(std::string const& project_id, + std::string const& instance_id, + std::string const& database_id) { + namespace spanner = ::google::cloud::spanner; + using ::google::cloud::Options; + using ::google::cloud::StatusOr; + + auto db = spanner::Database(project_id, instance_id, database_id); + + // The read lock mode specified at the client-level will be applied + // to all RW transactions. + auto options = Options{}.set( + spanner::Transaction::ReadLockMode::kOptimistic); + auto client = spanner::Client(spanner::MakeConnection(db, options)); + + auto commit = client.Commit( + [&client]( + spanner::Transaction const& txn) -> StatusOr { + // Read an AlbumTitle. + auto sql = spanner::SqlStatement( + "SELECT AlbumTitle from Albums WHERE SingerId = @SingerId and " + "AlbumId = @AlbumId", + {{"SingerId", spanner::Value(2)}, {"AlbumId", spanner::Value(1)}}); + auto rows = client.ExecuteQuery(txn, std::move(sql)); + for (auto const& row : + spanner::StreamOf>(rows)) { + if (!row) return row.status(); + std::cout << "Current Album Title: " << std::get<0>(*row) << "\n"; + } + + // Update the title. + auto update_sql = spanner::SqlStatement( + "UPDATE Albums " + "SET AlbumTitle = @AlbumTitle " + "WHERE SingerId = @SingerId and AlbumId = @AlbumId", + {{"AlbumTitle", spanner::Value("A New Title")}, + {"SingerId", spanner::Value(2)}, + {"AlbumId", spanner::Value(1)}}); + auto result = client.ExecuteDml(txn, std::move(update_sql)); + if (!result) return result.status(); + std::cout << result->RowsModified() << " record(s) updated.\n"; + + return spanner::Mutations{}; + }, + // The read lock mode specified at the transaction-level takes + // precedence over the read lock mode configured at the client-level. + // kPessimistic is used here to demonstrate overriding the client-level + // setting. + Options{}.set( + spanner::Transaction::ReadLockMode::kPessimistic)); + + if (!commit) throw std::move(commit).status(); + std::cout << "Update was successful [spanner_read_lock_mode]\n"; +} +//! [END spanner_read_lock_mode] + +void ReadLockModeSettingCommand(std::vector argv) { + if (argv.size() != 3) { + throw std::runtime_error( + "read-lock-mode-setting "); + } + ReadLockModeSetting(argv[0], argv[1], argv[2]); +} + //! [START spanner_get_commit_stats] void GetCommitStatistics(google::cloud::spanner::Client client) { namespace spanner = ::google::cloud::spanner; @@ -5258,6 +5323,7 @@ int RunOneCommand(std::vector argv) { ReadDataWithStoringIndex), make_command_entry("read-write-transaction", ReadWriteTransaction), {"isolation-level-setting", IsolationLevelSettingCommand}, + {"read-lock-mode-setting", ReadLockModeSettingCommand}, make_command_entry("get-commit-stats", GetCommitStatistics), make_command_entry("dml-standard-insert", DmlStandardInsert), make_command_entry("dml-standard-update", DmlStandardUpdate), diff --git a/google/cloud/spanner/transaction.cc b/google/cloud/spanner/transaction.cc index e4d76227fa45c..2a9b4803c11c5 100644 --- a/google/cloud/spanner/transaction.cc +++ b/google/cloud/spanner/transaction.cc @@ -93,6 +93,14 @@ google::spanner::v1::TransactionOptions MakeOpts( ProtoIsolationLevel(current.get())); } + if (opts.read_write().read_lock_mode() == + google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode:: + TransactionOptions_ReadWrite_ReadLockMode_READ_LOCK_MODE_UNSPECIFIED && + current.has()) { + opts.mutable_read_write()->set_read_lock_mode( + ProtoReadLockMode(current.get())); + } + return opts; } diff --git a/google/cloud/spanner/transaction_test.cc b/google/cloud/spanner/transaction_test.cc index b3ca3ba7cf819..11577fa494a87 100644 --- a/google/cloud/spanner/transaction_test.cc +++ b/google/cloud/spanner/transaction_test.cc @@ -217,6 +217,58 @@ TEST(Transaction, IsolationLevelNotSpecified) { }); } +TEST(Transaction, ReadLockModePrecedence) { + internal::OptionsSpan span(Options{}.set( + Transaction::ReadLockMode::kOptimistic)); + + // Case 1: Per-call overrides default options + auto opts = + Transaction::ReadWriteOptions(Transaction::ReadLockMode::kPessimistic); + Transaction txn = MakeReadWriteTransaction(opts); + spanner_internal::Visit( + txn, [](spanner_internal::SessionHolder&, + StatusOr& s, + spanner_internal::TransactionContext const&) { + EXPECT_EQ( + s->begin().read_write().read_lock_mode(), + google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode:: + TransactionOptions_ReadWrite_ReadLockMode_PESSIMISTIC); + return 0; + }); + + // Case 2: Fallback to default options + auto opts_default = Transaction::ReadWriteOptions(); + Transaction txn_default = MakeReadWriteTransaction(opts_default); + spanner_internal::Visit( + txn_default, [](spanner_internal::SessionHolder&, + StatusOr& s, + spanner_internal::TransactionContext const&) { + EXPECT_EQ( + s->begin().read_write().read_lock_mode(), + google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode:: + TransactionOptions_ReadWrite_ReadLockMode_OPTIMISTIC); + return 0; + }); +} + +TEST(Transaction, ReadLockModeNotSpecified) { + // Case: Read lock mode not specified in transaction options or default + // options + auto opts = Transaction::ReadWriteOptions(); + Transaction txn = MakeReadWriteTransaction(opts); + spanner_internal::Visit(txn, [](spanner_internal::SessionHolder&, + StatusOr< + google::spanner::v1::TransactionSelector>& + s, + spanner_internal::TransactionContext const&) { + EXPECT_EQ( + s->begin().read_write().read_lock_mode(), + google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode:: + TransactionOptions_ReadWrite_ReadLockMode_READ_LOCK_MODE_UNSPECIFIED); + return 0; + }); +} + TEST(Transaction, ReadWriteOptionsWithTag) { auto opts = Transaction::ReadWriteOptions().WithTag("test-tag"); Transaction txn = MakeReadWriteTransaction(opts);