Skip to content

Commit 7451e09

Browse files
committed
spanner: Refine Transaction::ReadWriteOptions with IsolationLevel
This commit improves the API design by integrating `IsolationLevel` directly into `Transaction::ReadWriteOptions`. Previously, `IsolationLevel` was passed as a separate argument to some `Transaction` constructors, even though it is semantically relevant only to read-write transactions. This could lead to ambiguity or misuse. By making `IsolationLevel` a member of `ReadWriteOptions`, the API more accurately reflects the underlying Spanner transaction semantics, enforcing that isolation levels are configured solely within the context of read-write transactions. This enhances type safety and improves the clarity of the client library's API. This change addresses reviewer feedback regarding the logical grouping of `IsolationLevel` within the `ReadWriteOptions` message.#
1 parent e7407c6 commit 7451e09

8 files changed

Lines changed: 90 additions & 83 deletions

File tree

google/cloud/spanner/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ add_library(
173173
internal/tuple_utils.h
174174
interval.cc
175175
interval.h
176-
isolation_level.h
177176
json.h
178177
keys.cc
179178
keys.h

google/cloud/spanner/google_cloud_cpp_spanner.bzl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ google_cloud_cpp_spanner_hdrs = [
9494
"internal/transaction_impl.h",
9595
"internal/tuple_utils.h",
9696
"interval.h",
97-
"isolation_level.h",
9897
"json.h",
9998
"keys.h",
10099
"lock_hint.h",

google/cloud/spanner/integration_tests/client_integration_test.cc

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
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/isolation_level.h"
2019
#include "google/cloud/spanner/options.h"
2120
#include "google/cloud/spanner/testing/database_integration_test.h"
2221
#include "google/cloud/credentials.h"
@@ -241,6 +240,9 @@ TEST_F(ClientIntegrationTest, MultipleInserts) {
241240
RowType(4, "test-fname-4", "test-lname-4")));
242241
}
243242

243+
// A smoke test to verify that the TransactionIsolationLevelOption is
244+
// correctly processed and accepted by the Spanner backend. It does not
245+
// test the behavioral semantics of the isolation level itself.
244246
/// @test Verify that TransactionIsolationLevel works as expected.
245247
TEST_F(ClientIntegrationTest, TransactionIsolationLevel) {
246248
auto& client = *client_;
@@ -251,15 +253,13 @@ TEST_F(ClientIntegrationTest, TransactionIsolationLevel) {
251253
for (auto const& row : rows) {
252254
if (!row) return row.status();
253255
}
254-
// std::cout << "Transaction is active." << std::endl;
255256
return Mutations{};
256257
},
257-
Options{}.set<TransactionIsolationLevelOption>(
258-
IsolationLevel::kRepeatableRead));
258+
Options{}.set<spanner::TransactionIsolationLevelOption>(
259+
Transaction::IsolationLevel::kRepeatableRead));
259260
EXPECT_THAT(commit, StatusIs(StatusCode::kOk));
260261
}
261262

262-
263263
/// @test Verify that Client::Rollback works as expected.
264264
TEST_F(ClientIntegrationTest, TransactionRollback) {
265265
ASSERT_NO_FATAL_FAILURE(InsertTwoSingers());
@@ -1419,6 +1419,9 @@ TEST_F(PgClientIntegrationTest, FineGrainedAccessControl) {
14191419
ASSERT_STATUS_OK(metadata);
14201420
}
14211421

1422+
// A smoke test to verify that the TransactionIsolationLevelOption is
1423+
// correctly processed and accepted by the Spanner backend. It does not
1424+
// test the behavioral semantics of the isolation level itself.
14221425
/// @test Verify that TransactionIsolationLevel works as expected.
14231426
TEST_F(PgClientIntegrationTest, TransactionIsolationLevel) {
14241427
auto& client = *client_;
@@ -1430,8 +1433,8 @@ TEST_F(PgClientIntegrationTest, TransactionIsolationLevel) {
14301433
}
14311434
return Mutations{};
14321435
},
1433-
Options{}.set<TransactionIsolationLevelOption>(
1434-
IsolationLevel::kRepeatableRead));
1436+
Options{}.set<spanner::TransactionIsolationLevelOption>(
1437+
Transaction::IsolationLevel::kRepeatableRead));
14351438
EXPECT_THAT(commit, StatusIs(StatusCode::kOk));
14361439
}
14371440

google/cloud/spanner/isolation_level.h

Lines changed: 0 additions & 43 deletions
This file was deleted.

google/cloud/spanner/options.h

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@
4141
#include "google/cloud/spanner/backoff_policy.h"
4242
#include "google/cloud/spanner/directed_read_replicas.h"
4343
#include "google/cloud/spanner/internal/session.h"
44-
#include "google/cloud/spanner/isolation_level.h"
4544
#include "google/cloud/spanner/lock_hint.h"
4645
#include "google/cloud/spanner/order_by.h"
4746
#include "google/cloud/spanner/polling_policy.h"
4847
#include "google/cloud/spanner/request_priority.h"
4948
#include "google/cloud/spanner/retry_policy.h"
49+
#include "google/cloud/spanner/transaction.h"
5050
#include "google/cloud/spanner/version.h"
5151
#include "google/cloud/options.h"
5252
#include "absl/types/variant.h"
@@ -417,23 +417,22 @@ struct ExcludeTransactionFromChangeStreamsOption {
417417
};
418418

419419
/**
420-
* Option for `google::cloud::Options` to return additional statistics
421-
* about the committed transaction in a `spanner::CommitResult`.
420+
* Option for `google::cloud::Options` to set the transaction isolation level.
422421
*
423422
* @ingroup google-cloud-spanner-options
424423
*/
425-
struct CommitReturnStatsOption {
426-
using Type = bool;
424+
struct TransactionIsolationLevelOption {
425+
using Type = spanner::Transaction::IsolationLevel;
427426
};
428427

429428
/**
430-
* Option for `google::cloud::Options` to set the isolation level for
431-
* read-write transactions.
429+
* Option for `google::cloud::Options` to return additional statistics
430+
* about the committed transaction in a `spanner::CommitResult`.
432431
*
433432
* @ingroup google-cloud-spanner-options
434433
*/
435-
struct TransactionIsolationLevelOption {
436-
using Type = IsolationLevel;
434+
struct CommitReturnStatsOption {
435+
using Type = bool;
437436
};
438437

439438
/**

google/cloud/spanner/transaction.cc

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ ProtoReadLockMode(
5454
}
5555
}
5656

57+
google::spanner::v1::TransactionOptions_IsolationLevel ProtoIsolationLevel(
58+
absl::optional<Transaction::IsolationLevel> const& isolation_level) {
59+
if (!isolation_level) {
60+
return google::spanner::v1::TransactionOptions::ISOLATION_LEVEL_UNSPECIFIED;
61+
}
62+
switch (*isolation_level) {
63+
case Transaction::IsolationLevel::kSerializable:
64+
return google::spanner::v1::TransactionOptions::SERIALIZABLE;
65+
case Transaction::IsolationLevel::kRepeatableRead:
66+
return google::spanner::v1::TransactionOptions::REPEATABLE_READ;
67+
default:
68+
return google::spanner::v1::TransactionOptions::
69+
ISOLATION_LEVEL_UNSPECIFIED;
70+
}
71+
}
72+
5773
google::spanner::v1::TransactionOptions MakeOpts(
5874
google::spanner::v1::TransactionOptions_ReadOnly ro_opts) {
5975
google::spanner::v1::TransactionOptions opts;
@@ -63,21 +79,21 @@ google::spanner::v1::TransactionOptions MakeOpts(
6379

6480
google::spanner::v1::TransactionOptions MakeOpts(
6581
google::spanner::v1::TransactionOptions_ReadWrite rw_opts,
66-
IsolationLevel isolation_level) {
82+
absl::optional<Transaction::IsolationLevel> isolation_level) {
6783
google::spanner::v1::TransactionOptions opts;
6884
*opts.mutable_read_write() = std::move(rw_opts);
6985
auto const& current = internal::CurrentOptions();
7086
if (current.get<ExcludeTransactionFromChangeStreamsOption>()) {
7187
opts.set_exclude_txn_from_change_streams(true);
7288
}
73-
if (isolation_level == IsolationLevel::kUnspecified) {
74-
isolation_level = current.get<TransactionIsolationLevelOption>();
75-
}
76-
if (isolation_level != IsolationLevel::kUnspecified) {
89+
if (isolation_level &&
90+
*isolation_level != Transaction::IsolationLevel::kUnspecified) {
91+
opts.set_isolation_level(ProtoIsolationLevel(isolation_level));
92+
} else if (current.has<TransactionIsolationLevelOption>()) {
7793
opts.set_isolation_level(
78-
static_cast<google::spanner::v1::TransactionOptions::IsolationLevel>(
79-
isolation_level));
94+
ProtoIsolationLevel(current.get<TransactionIsolationLevelOption>()));
8095
}
96+
8197
return opts;
8298
}
8399

@@ -112,6 +128,13 @@ Transaction::ReadWriteOptions& Transaction::ReadWriteOptions::WithTag(
112128
return *this;
113129
}
114130

131+
Transaction::ReadWriteOptions&
132+
Transaction::ReadWriteOptions::WithIsolationLevel(
133+
IsolationLevel isolation_level) {
134+
isolation_level_ = isolation_level;
135+
return *this;
136+
}
137+
115138
Transaction::SingleUseOptions::SingleUseOptions(ReadOnlyOptions opts) {
116139
ro_opts_ = std::move(opts.ro_opts_);
117140
}
@@ -146,6 +169,17 @@ Transaction::Transaction(ReadWriteOptions opts) {
146169
std::move(opts.tag_).value_or(std::string()));
147170
}
148171

172+
Transaction::Transaction(ReadWriteOptions opts,
173+
IsolationLevel isolation_level) {
174+
google::spanner::v1::TransactionSelector selector;
175+
*selector.mutable_begin() =
176+
MakeOpts(std::move(opts.rw_opts_), isolation_level);
177+
auto const route_to_leader = true; // read-write
178+
impl_ = std::make_shared<spanner_internal::TransactionImpl>(
179+
std::move(selector), route_to_leader,
180+
std::move(opts.tag_).value_or(std::string()));
181+
}
182+
149183
Transaction::Transaction(Transaction const& txn, ReadWriteOptions opts) {
150184
google::spanner::v1::TransactionSelector selector;
151185
*selector.mutable_begin() =

google/cloud/spanner/transaction.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include "google/cloud/spanner/internal/transaction_impl.h"
1919
#include "google/cloud/spanner/timestamp.h"
2020
#include "google/cloud/spanner/version.h"
21-
#include "google/cloud/spanner/isolation_level.h"
2221
#include "absl/types/optional.h"
2322
#include <google/spanner/v1/transaction.pb.h>
2423
#include <chrono>
@@ -58,6 +57,12 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
5857
*/
5958
class Transaction {
6059
public:
60+
enum class IsolationLevel {
61+
kUnspecified,
62+
kSerializable,
63+
kRepeatableRead,
64+
};
65+
6166
/**
6267
* Options for ReadOnly transactions.
6368
*/
@@ -101,13 +106,13 @@ class Transaction {
101106

102107
explicit ReadWriteOptions(ReadLockMode read_lock_mode);
103108

109+
explicit ReadWriteOptions(IsolationLevel isolation_level)
110+
: isolation_level_(isolation_level) {}
111+
104112
// A tag used for collecting statistics about the transaction.
105113
ReadWriteOptions& WithTag(absl::optional<std::string> tag);
106114

107-
ReadWriteOptions& WithIsolationLevel(IsolationLevel isolation_level) {
108-
isolation_level_ = isolation_level;
109-
return *this;
110-
}
115+
ReadWriteOptions& WithIsolationLevel(IsolationLevel isolation_level);
111116

112117
private:
113118
friend Transaction;
@@ -157,6 +162,8 @@ class Transaction {
157162
/// @copydoc Transaction(ReadOnlyOptions)
158163
explicit Transaction(ReadWriteOptions opts);
159164
/// @copydoc Transaction(ReadOnlyOptions)
165+
explicit Transaction(ReadWriteOptions opts, IsolationLevel isolation_level);
166+
/// @copydoc Transaction(ReadOnlyOptions)
160167
Transaction(Transaction const& txn, ReadWriteOptions opts);
161168
///@}
162169

google/cloud/spanner/transaction_test.cc

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ TEST(Transaction, MultiplexedPreviousTransactionId) {
173173

174174
TEST(Transaction, IsolationLevel) {
175175
auto opts = Transaction::ReadWriteOptions().WithIsolationLevel(
176-
IsolationLevel::kRepeatableRead);
176+
Transaction::IsolationLevel::kRepeatableRead);
177177
Transaction txn = MakeReadWriteTransaction(opts);
178178
spanner_internal::Visit(
179179
txn, [](spanner_internal::SessionHolder&,
@@ -183,29 +183,24 @@ TEST(Transaction, IsolationLevel) {
183183
EXPECT_TRUE(s->begin().has_read_write());
184184
EXPECT_EQ(s->begin().isolation_level(),
185185
google::spanner::v1::TransactionOptions::REPEATABLE_READ);
186-
187-
std::cout << "Isolation Level: "
188-
<< s->begin().isolation_level() << std::endl;
189186
return 0;
190187
});
191188
}
192189

193190
TEST(Transaction, IsolationLevelPrecedence) {
194191
internal::OptionsSpan span(Options{}.set<TransactionIsolationLevelOption>(
195-
IsolationLevel::kSerializable));
192+
Transaction::IsolationLevel::kSerializable));
196193

197194
// Case 1: Per-call overrides client default
198195
auto opts = Transaction::ReadWriteOptions().WithIsolationLevel(
199-
IsolationLevel::kRepeatableRead);
196+
Transaction::IsolationLevel::kRepeatableRead);
200197
Transaction txn = MakeReadWriteTransaction(opts);
201198
spanner_internal::Visit(
202199
txn, [](spanner_internal::SessionHolder&,
203200
StatusOr<google::spanner::v1::TransactionSelector>& s,
204201
spanner_internal::TransactionContext const&) {
205202
EXPECT_EQ(s->begin().isolation_level(),
206203
google::spanner::v1::TransactionOptions::REPEATABLE_READ);
207-
std::cout << "Isolation Level: "
208-
<< s->begin().isolation_level() << std::endl;
209204
return 0;
210205
});
211206

@@ -218,8 +213,6 @@ TEST(Transaction, IsolationLevelPrecedence) {
218213
spanner_internal::TransactionContext const&) {
219214
EXPECT_EQ(s->begin().isolation_level(),
220215
google::spanner::v1::TransactionOptions::SERIALIZABLE);
221-
std::cout << "Isolation Level: "
222-
<< s->begin().isolation_level() << std::endl;
223216
return 0;
224217
});
225218
}
@@ -265,6 +258,22 @@ TEST(Transaction, ReadWriteOptionsWithReadLockMode) {
265258
google::spanner::v1::TransactionOptions_ReadWrite::OPTIMISTIC);
266259
}
267260

261+
TEST(Transaction, ReadWriteOptionsWithIsolationLevel) {
262+
auto opts = Transaction::ReadWriteOptions(
263+
Transaction::IsolationLevel::kRepeatableRead);
264+
Transaction txn(opts, Transaction::IsolationLevel::kRepeatableRead);
265+
spanner_internal::Visit(
266+
txn, [](spanner_internal::SessionHolder&,
267+
StatusOr<google::spanner::v1::TransactionSelector>& s,
268+
spanner_internal::TransactionContext const&) {
269+
EXPECT_TRUE(s->has_begin());
270+
EXPECT_TRUE(s->begin().has_read_write());
271+
EXPECT_EQ(s->begin().isolation_level(),
272+
google::spanner::v1::TransactionOptions::REPEATABLE_READ);
273+
return 0;
274+
});
275+
}
276+
268277
} // namespace
269278
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
270279
} // namespace spanner

0 commit comments

Comments
 (0)