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);