diff --git a/google/cloud/spanner/integration_tests/client_integration_test.cc b/google/cloud/spanner/integration_tests/client_integration_test.cc index 4a913e89d1434..29077292f756b 100644 --- a/google/cloud/spanner/integration_tests/client_integration_test.cc +++ b/google/cloud/spanner/integration_tests/client_integration_test.cc @@ -16,6 +16,7 @@ #include "google/cloud/spanner/client.h" #include "google/cloud/spanner/database.h" #include "google/cloud/spanner/mutations.h" +#include "google/cloud/spanner/options.h" #include "google/cloud/spanner/testing/database_integration_test.h" #include "google/cloud/credentials.h" #include "google/cloud/internal/getenv.h" @@ -787,6 +788,34 @@ void CheckExecuteQueryWithSingleUseOptions( EXPECT_THAT(actual_rows, UnorderedElementsAreArray(expected_rows)); } +TEST_F(ClientIntegrationTest, ReadLockModeOptionIsSent) { + auto const singer_id = 101; + auto mutation_helper = [singer_id](std::string const& new_name) { + return Mutations{MakeInsertOrUpdateMutation( + "Singers", {"SingerId", "FirstName"}, singer_id, new_name)}; + }; + + // Initial insert + auto insert = client_->Commit(mutation_helper("InitialName")); + ASSERT_STATUS_OK(insert); + + auto read_lock_mode = Transaction::ReadLockMode::kOptimistic; + auto tx_a = + MakeReadWriteTransaction(Transaction::ReadWriteOptions(read_lock_mode)); + auto tx_a_read_result = client_->Read( + tx_a, "Singers", KeySet().AddKey(MakeKey(singer_id)), {"SingerId"}); + for (auto const& row : StreamOf>(tx_a_read_result)) { + EXPECT_STATUS_OK(row); + } + tx_a = MakeReadWriteTransaction( + tx_a, Transaction::ReadWriteOptions(read_lock_mode)); + + auto optimistic_result = + client_->Commit(tx_a, mutation_helper("SecondModifiedName")); + + EXPECT_STATUS_OK(optimistic_result); +} + /// @test Test ExecuteQuery() with bounded staleness set by a timestamp. TEST_F(ClientIntegrationTest, ExecuteQueryBoundedStalenessTimestamp) { CheckExecuteQueryWithSingleUseOptions( diff --git a/google/cloud/spanner/transaction.cc b/google/cloud/spanner/transaction.cc index 822a54f7bf4f5..07d9c9edd464d 100644 --- a/google/cloud/spanner/transaction.cc +++ b/google/cloud/spanner/transaction.cc @@ -34,6 +34,26 @@ google::protobuf::Duration ToProto(std::chrono::nanoseconds ns) { return proto; } +google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode +ProtoReadLockMode( + absl::optional const& read_lock_mode) { + if (!read_lock_mode) { + return google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode:: + TransactionOptions_ReadWrite_ReadLockMode_READ_LOCK_MODE_UNSPECIFIED; + } + switch (*read_lock_mode) { + case Transaction::ReadLockMode::kOptimistic: + return google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode:: + TransactionOptions_ReadWrite_ReadLockMode_OPTIMISTIC; + case Transaction::ReadLockMode::kPessimistic: + return google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode:: + TransactionOptions_ReadWrite_ReadLockMode_PESSIMISTIC; + default: + return google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode:: + TransactionOptions_ReadWrite_ReadLockMode_READ_LOCK_MODE_UNSPECIFIED; + } +} + google::spanner::v1::TransactionOptions MakeOpts( google::spanner::v1::TransactionOptions_ReadOnly ro_opts) { google::spanner::v1::TransactionOptions opts; @@ -71,7 +91,11 @@ Transaction::ReadOnlyOptions::ReadOnlyOptions( ro_opts_.set_return_read_timestamp(true); } -Transaction::ReadWriteOptions::ReadWriteOptions() = default; // currently none +Transaction::ReadWriteOptions::ReadWriteOptions() = default; + +Transaction::ReadWriteOptions::ReadWriteOptions(ReadLockMode read_lock_mode) { + rw_opts_.set_read_lock_mode(ProtoReadLockMode(read_lock_mode)); +} Transaction::ReadWriteOptions& Transaction::ReadWriteOptions::WithTag( absl::optional tag) { diff --git a/google/cloud/spanner/transaction.h b/google/cloud/spanner/transaction.h index 706467df14c5c..0949d52229a7f 100644 --- a/google/cloud/spanner/transaction.h +++ b/google/cloud/spanner/transaction.h @@ -78,14 +78,28 @@ class Transaction { google::spanner::v1::TransactionOptions_ReadOnly ro_opts_; }; + /** + * Read lock mode for ReadWrite transactions + * The Spanner V1 Transaction proto classes have their own enum + * implementations. + * See google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode + * This is a shorthand convenience for the developer. + */ + enum class ReadLockMode { + kUnspecified, + kPessimistic, + kOptimistic, + }; + /** * Options for ReadWrite transactions. */ class ReadWriteOptions { public: - // There are currently no read-write options. ReadWriteOptions(); + explicit ReadWriteOptions(ReadLockMode read_lock_mode); + // A tag used for collecting statistics about the transaction. ReadWriteOptions& WithTag(absl::optional tag); diff --git a/google/cloud/spanner/transaction_test.cc b/google/cloud/spanner/transaction_test.cc index 6d76e8fdf1872..ac7a3b46483b5 100644 --- a/google/cloud/spanner/transaction_test.cc +++ b/google/cloud/spanner/transaction_test.cc @@ -33,6 +33,8 @@ TEST(TransactionOptions, Construction) { Transaction::ReadOnlyOptions exact_dur(staleness); Transaction::ReadWriteOptions none; + Transaction::ReadWriteOptions rw_with_read_lock( + Transaction::ReadLockMode::kOptimistic); Transaction::SingleUseOptions su_strong(strong); Transaction::SingleUseOptions su_exact_ts(exact_ts); @@ -167,6 +169,47 @@ TEST(Transaction, MultiplexedPreviousTransactionId) { }); } +TEST(Transaction, ReadWriteOptionsWithTag) { + auto opts = Transaction::ReadWriteOptions().WithTag("test-tag"); + Transaction txn = MakeReadWriteTransaction(opts); + spanner_internal::Visit( + txn, [&](spanner_internal::SessionHolder& /*session*/, + StatusOr& s, + spanner_internal::TransactionContext const& ctx) { + EXPECT_TRUE(s->has_begin()); + EXPECT_TRUE(s->begin().has_read_write()); + EXPECT_EQ(ctx.tag, "test-tag"); + return 0; + }); +} + +TEST(Transaction, ReadWriteOptionsWithReadLockMode) { + auto check_lock_mode = + [](Transaction::ReadLockMode mode, + google::spanner::v1::TransactionOptions_ReadWrite_ReadLockMode + expected_proto_mode) { + auto opts = Transaction::ReadWriteOptions(mode); + Transaction txn = MakeReadWriteTransaction(opts); + spanner_internal::Visit( + txn, [&](spanner_internal::SessionHolder& /*session*/, + StatusOr& s, + spanner_internal::TransactionContext const& /*ctx*/) { + EXPECT_TRUE(s->has_begin()); + EXPECT_TRUE(s->begin().has_read_write()); + EXPECT_EQ(s->begin().read_write().read_lock_mode(), + expected_proto_mode); + return 0; + }); + }; + + check_lock_mode( + Transaction::ReadLockMode::kPessimistic, + google::spanner::v1::TransactionOptions_ReadWrite::PESSIMISTIC); + check_lock_mode( + Transaction::ReadLockMode::kOptimistic, + google::spanner::v1::TransactionOptions_ReadWrite::OPTIMISTIC); +} + } // namespace GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace spanner