Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions google/cloud/spanner/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
66 changes: 66 additions & 0 deletions google/cloud/spanner/samples/samples.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3539,6 +3539,71 @@ void IsolationLevelSettingCommand(std::vector<std::string> 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::TransactionReadLockModeOption>(
spanner::Transaction::ReadLockMode::kOptimistic);
auto client = spanner::Client(spanner::MakeConnection(db, options));

auto commit = client.Commit(
[&client](
spanner::Transaction const& txn) -> StatusOr<spanner::Mutations> {
// 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<std::tuple<std::string>>(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::TransactionReadLockModeOption>(
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<std::string> argv) {
if (argv.size() != 3) {
throw std::runtime_error(
"read-lock-mode-setting <project-id> <instance-id> <database-id>");
}
ReadLockModeSetting(argv[0], argv[1], argv[2]);
}

//! [START spanner_get_commit_stats]
void GetCommitStatistics(google::cloud::spanner::Client client) {
namespace spanner = ::google::cloud::spanner;
Expand Down Expand Up @@ -5258,6 +5323,7 @@ int RunOneCommand(std::vector<std::string> 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),
Expand Down
8 changes: 8 additions & 0 deletions google/cloud/spanner/transaction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ google::spanner::v1::TransactionOptions MakeOpts(
ProtoIsolationLevel(current.get<TransactionIsolationLevelOption>()));
}

if (opts.read_write().read_lock_mode() ==
google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode::
TransactionOptions_ReadWrite_ReadLockMode_READ_LOCK_MODE_UNSPECIFIED &&
current.has<TransactionReadLockModeOption>()) {
opts.mutable_read_write()->set_read_lock_mode(
ProtoReadLockMode(current.get<TransactionReadLockModeOption>()));
}

return opts;
}

Expand Down
52 changes: 52 additions & 0 deletions google/cloud/spanner/transaction_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,58 @@ TEST(Transaction, IsolationLevelNotSpecified) {
});
}

TEST(Transaction, ReadLockModePrecedence) {
internal::OptionsSpan span(Options{}.set<TransactionReadLockModeOption>(
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<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_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<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_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);
Expand Down
Loading