1616#include " google/cloud/spanner/client.h"
1717#include " google/cloud/spanner/database.h"
1818#include " google/cloud/spanner/mutations.h"
19+ #include " google/cloud/spanner/options.h"
1920#include " google/cloud/spanner/testing/database_integration_test.h"
2021#include " google/cloud/credentials.h"
2122#include " google/cloud/internal/random.h"
23+ #include " google/cloud/log.h"
2224#include " google/cloud/testing_util/status_matchers.h"
2325#include < gmock/gmock.h>
2426#include < map>
@@ -36,6 +38,7 @@ using ::google::cloud::testing_util::IsOk;
3638using ::google::cloud::testing_util::StatusIs;
3739using ::testing::AllOf;
3840using ::testing::AnyOf;
41+ using ::testing::Contains;
3942using ::testing::Eq;
4043using ::testing::Ge;
4144using ::testing::HasSubstr;
@@ -768,6 +771,49 @@ void CheckExecuteQueryWithSingleUseOptions(
768771 EXPECT_THAT (actual_rows, UnorderedElementsAreArray (expected_rows));
769772}
770773
774+ // / @test Verify the `ReadLockMode` option is sent in the RPC by creating a
775+ // / situation where a transaction A perfomrs a commit while a transaction B
776+ // / performed one after tx A started.
777+ TEST_F (ClientIntegrationTest, ReadLockModeOptionIsSent) {
778+ if (UsingEmulator ()) {
779+ GTEST_SKIP () << " Optimistic locks not supported by emulator" ;
780+ }
781+ auto const singer_id = 101 ;
782+
783+ auto mutation_helper = [singer_id] (std::string new_name) {
784+ return Mutations{MakeInsertOrUpdateMutation (" Singers" , {" SingerId" , " FirstName" }, singer_id, new_name)};
785+ };
786+
787+ // initial insert
788+ auto insert = client_->Commit (mutation_helper (" InitialName" ));
789+ ASSERT_STATUS_OK (insert);
790+
791+ // transaction A will do a DML after another transaction has executed DML
792+ // If optimistic, we expect transaction A to be aborted
793+ auto test_helper = [&mutation_helper, singer_id] (Transaction::ReadLockMode read_lock_mode) -> StatusOr<CommitResult> {
794+ // here we create tx A and confirm it can perform a read.
795+ auto tx_a = MakeReadWriteTransaction (Transaction::ReadWriteOptions (read_lock_mode));
796+ auto tx_a_read_result = client_->Read (tx_a, " Singers" , KeySet ().AddKey (MakeKey (singer_id)), {" SingerId" });
797+ for (auto row : StreamOf<std::tuple<std::int64_t >>(tx_a_read_result)) {
798+ EXPECT_STATUS_OK (row);
799+ }
800+
801+ // now a separate tx b will perform a write operation before tx A is finished.
802+ auto tx_b = client_->Commit (mutation_helper (" FirstModifiedName" ));
803+ EXPECT_STATUS_OK (tx_b);
804+
805+ // depending on the read lock mode, the result of the next write operation
806+ // in tx A will vary.
807+ return client_->Commit (tx_a, mutation_helper (" SecondModifiedName" ));
808+ };
809+
810+ auto optimistic_result = test_helper (Transaction::ReadLockMode::kOptimistic );
811+ auto pessimistic_result = test_helper (Transaction::ReadLockMode::kPessimistic );
812+
813+ EXPECT_STATUS_OK (pessimistic_result);
814+ EXPECT_THAT (optimistic_result, StatusIs (StatusCode::kAborted ));
815+ }
816+
771817// / @test Test ExecuteQuery() with bounded staleness set by a timestamp.
772818TEST_F (ClientIntegrationTest, ExecuteQueryBoundedStalenessTimestamp) {
773819 CheckExecuteQueryWithSingleUseOptions (
0 commit comments