Skip to content

Commit 765b863

Browse files
committed
feat(spanner): add support for lock_hint enum
1 parent 8727ff0 commit 765b863

File tree

11 files changed

+176
-13
lines changed

11 files changed

+176
-13
lines changed

google/cloud/spanner/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ add_library(
176176
json.h
177177
keys.cc
178178
keys.h
179+
lock_hint.h
179180
mutations.cc
180181
mutations.h
181182
numeric.cc

google/cloud/spanner/client.cc

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@ RowStream Client::Read(std::string table, KeySet keys,
5454
opts = internal::MergeOptions(std::move(opts), opts_);
5555
auto directed_read_option = ExtractOpt<DirectedReadOption>(opts);
5656
internal::OptionsSpan span(std::move(opts));
57+
auto lock_hint = ExtractOpt<LockHintOption>(opts);
5758
return conn_->Read({spanner_internal::MakeSingleUseTransaction(
5859
Transaction::ReadOnlyOptions()),
5960
std::move(table), std::move(keys), std::move(columns),
6061
ToReadOptions(internal::CurrentOptions()), absl::nullopt,
61-
false, std::move(directed_read_option)});
62+
false, std::move(directed_read_option),
63+
std::move(lock_hint)});
6264
}
6365

6466
RowStream Client::Read(Transaction::SingleUseOptions transaction_options,
@@ -67,30 +69,35 @@ RowStream Client::Read(Transaction::SingleUseOptions transaction_options,
6769
opts = internal::MergeOptions(std::move(opts), opts_);
6870
auto directed_read_option = ExtractOpt<DirectedReadOption>(opts);
6971
internal::OptionsSpan span(std::move(opts));
72+
auto lock_hint = ExtractOpt<LockHintOption>(opts);
7073
return conn_->Read({spanner_internal::MakeSingleUseTransaction(
7174
std::move(transaction_options)),
7275
std::move(table), std::move(keys), std::move(columns),
7376
ToReadOptions(internal::CurrentOptions()), absl::nullopt,
74-
false, std::move(directed_read_option)});
77+
false, std::move(directed_read_option),
78+
std::move(lock_hint)});
7579
}
7680

7781
RowStream Client::Read(Transaction transaction, std::string table, KeySet keys,
7882
std::vector<std::string> columns, Options opts) {
7983
opts = internal::MergeOptions(std::move(opts), opts_);
8084
auto directed_read_option = ExtractOpt<DirectedReadOption>(opts);
85+
auto lock_hint = ExtractOpt<LockHintOption>(opts);
8186
internal::OptionsSpan span(std::move(opts));
8287
return conn_->Read({std::move(transaction), std::move(table), std::move(keys),
8388
std::move(columns),
8489
ToReadOptions(internal::CurrentOptions()), absl::nullopt,
85-
false, std::move(directed_read_option)});
90+
false, std::move(directed_read_option),
91+
std::move(lock_hint)});
8692
}
8793

8894
RowStream Client::Read(ReadPartition const& read_partition, Options opts) {
8995
opts = internal::MergeOptions(std::move(opts), opts_);
9096
auto directed_read_option = ExtractOpt<DirectedReadOption>(opts);
97+
auto lock_hint = ExtractOpt<LockHintOption>(opts);
9198
internal::OptionsSpan span(std::move(opts));
9299
return conn_->Read(spanner_internal::MakeReadParams(
93-
read_partition, std::move(directed_read_option)));
100+
read_partition, std::move(directed_read_option), std::move(lock_hint)));
94101
}
95102

96103
StatusOr<std::vector<ReadPartition>> Client::PartitionRead(
@@ -100,7 +107,8 @@ StatusOr<std::vector<ReadPartition>> Client::PartitionRead(
100107
return conn_->PartitionRead(
101108
{{std::move(transaction), std::move(table), std::move(keys),
102109
std::move(columns), ToReadOptions(internal::CurrentOptions()),
103-
absl::nullopt, false, DirectedReadOption::Type{}},
110+
absl::nullopt, false, DirectedReadOption::Type{},
111+
LockHint::kLockHintUnspecified},
104112
ToPartitionOptions(internal::CurrentOptions())});
105113
}
106114

google/cloud/spanner/client_test.cc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,8 @@ TEST(ClientTest, CommitMutatorSuccess) {
404404

405405
auto conn = std::make_shared<MockConnection>();
406406
Transaction txn = MakeReadWriteTransaction(); // placeholder
407-
Connection::ReadParams actual_read_params{txn, {}, {}, {}, {}, {}, {}, {}};
407+
Connection::ReadParams actual_read_params{txn, {}, {}, {}, {},
408+
{}, {}, {}, {}};
408409
Connection::CommitParams actual_commit_params{txn, {}, {}};
409410

410411
auto source = std::make_unique<MockResultSetSource>();
@@ -453,7 +454,8 @@ TEST(ClientTest, CommitMutatorSuccess) {
453454
TEST(ClientTest, CommitMutatorRollback) {
454455
auto conn = std::make_shared<MockConnection>();
455456
Transaction txn = MakeReadWriteTransaction(); // placeholder
456-
Connection::ReadParams actual_read_params{txn, {}, {}, {}, {}, {}, {}, {}};
457+
Connection::ReadParams actual_read_params{txn, {}, {}, {}, {},
458+
{}, {}, {}, {}};
457459

458460
auto source = std::make_unique<MockResultSetSource>();
459461
auto constexpr kText = R"pb(
@@ -495,7 +497,8 @@ TEST(ClientTest, CommitMutatorRollback) {
495497
TEST(ClientTest, CommitMutatorRollbackError) {
496498
auto conn = std::make_shared<MockConnection>();
497499
Transaction txn = MakeReadWriteTransaction(); // placeholder
498-
Connection::ReadParams actual_read_params{txn, {}, {}, {}, {}, {}, {}, {}};
500+
Connection::ReadParams actual_read_params{txn, {}, {}, {}, {},
501+
{}, {}, {}, {}};
499502

500503
auto source = std::make_unique<MockResultSetSource>();
501504
auto constexpr kText = R"pb(

google/cloud/spanner/connection.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "google/cloud/spanner/commit_options.h"
2020
#include "google/cloud/spanner/commit_result.h"
2121
#include "google/cloud/spanner/keys.h"
22+
#include "google/cloud/spanner/lock_hint.h"
2223
#include "google/cloud/spanner/mutations.h"
2324
#include "google/cloud/spanner/options.h"
2425
#include "google/cloud/spanner/partition_options.h"
@@ -81,6 +82,7 @@ class Connection {
8182
absl::optional<std::string> partition_token;
8283
bool partition_data_boost = false; // when partition_token
8384
DirectedReadOption::Type directed_read_option;
85+
LockHint lock_hint;
8486
};
8587

8688
/// Wrap the arguments to `PartitionRead()`.

google/cloud/spanner/google_cloud_cpp_spanner.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ google_cloud_cpp_spanner_hdrs = [
9696
"interval.h",
9797
"json.h",
9898
"keys.h",
99+
"lock_hint.h",
99100
"mutations.h",
100101
"numeric.h",
101102
"oid.h",

google/cloud/spanner/internal/connection_impl.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,21 @@ google::spanner::v1::RequestOptions_Priority ProtoRequestPriority(
190190
return google::spanner::v1::RequestOptions::PRIORITY_UNSPECIFIED;
191191
}
192192

193+
google::spanner::v1::ReadRequest_LockHint ProtoLockHint(
194+
absl::optional<spanner::LockHint> const& order_by) {
195+
if (order_by) {
196+
switch (*order_by) {
197+
case spanner::LockHint::kLockHintUnspecified:
198+
return google::spanner::v1::ReadRequest_LockHint_LOCK_HINT_UNSPECIFIED;
199+
case spanner::LockHint::kLockHintShared:
200+
return google::spanner::v1::ReadRequest_LockHint_LOCK_HINT_SHARED;
201+
case spanner::LockHint::kLockHintExclusive:
202+
return google::spanner::v1::ReadRequest_LockHint_LOCK_HINT_EXCLUSIVE;
203+
}
204+
}
205+
return google::spanner::v1::ReadRequest_LockHint_LOCK_HINT_UNSPECIFIED;
206+
}
207+
193208
// Converts a `google::protobuf::Timestamp` to a `spanner::Timestamp`, but
194209
// substitutes the maximal value for any conversion error. This is needed
195210
// when, for example, a response commit_timestamp is out of range but the
@@ -548,6 +563,7 @@ spanner::RowStream ConnectionImpl::ReadImpl(
548563
*request->mutable_transaction() = *s;
549564
request->set_table(std::move(params.table));
550565
request->set_index(std::move(params.read_options.index_name));
566+
request->set_lock_hint(ProtoLockHint(params.lock_hint));
551567
for (auto&& column : params.columns) {
552568
request->add_columns(std::move(column));
553569
}

google/cloud/spanner/internal/connection_impl_test.cc

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ MATCHER_P(HasReplicaType, type, "has replica type") {
211211
return arg.type() == type;
212212
}
213213

214+
MATCHER_P(HasLockHint, lock_hint, "has lock_hint") {
215+
return arg.lock_hint() == lock_hint;
216+
}
217+
214218
// Ideally this would be a matcher, but matcher args are `const` and `RowStream`
215219
// only has non-const methods.
216220
bool ContainsNoRows(spanner::RowStream& rows) {
@@ -4091,6 +4095,85 @@ TEST(ConnectionImplTest, RollbackSessionNotFound) {
40914095
EXPECT_THAT(txn, HasBadSession());
40924096
}
40934097

4098+
TEST(ConnectionImplTest, ReadRequestLockHintParameterUnspecified) {
4099+
auto mock = std::make_shared<spanner_testing::MockSpannerStub>();
4100+
auto db = spanner::Database("project", "instance", "database");
4101+
EXPECT_CALL(*mock, BatchCreateSessions(_, _, HasDatabase(db)))
4102+
.WillOnce(Return(MakeSessionsResponse({"test-session-name"})));
4103+
EXPECT_CALL(*mock,
4104+
AsyncDeleteSession(_, _, _, HasSessionName("test-session-name")))
4105+
.WillOnce(Return(make_ready_future(Status{})));
4106+
Sequence s;
4107+
EXPECT_CALL(
4108+
*mock,
4109+
StreamingRead(
4110+
_, _,
4111+
AllOf(HasSession("test-session-name"),
4112+
HasLockHint(
4113+
google::spanner::v1::ReadRequest::LOCK_HINT_UNSPECIFIED))))
4114+
.InSequence(s)
4115+
.WillOnce(Return(ByMove(MakeReader<google::spanner::v1::PartialResultSet>(
4116+
{R"pb(metadata: { transaction: { id: "txn1" } })pb"}))));
4117+
4118+
auto conn = MakeConnectionImpl(db, mock);
4119+
internal::OptionsSpan span(MakeLimitedTimeOptions());
4120+
4121+
// Scenario 1: No explicit OrderBy (should map to UNSPECIFIED)
4122+
spanner::ReadOptions read_options;
4123+
spanner::Transaction txn1 =
4124+
MakeReadOnlyTransaction(spanner::Transaction::ReadOnlyOptions());
4125+
auto rows1 = conn->Read(
4126+
{txn1, "table", spanner::KeySet::All(), {"col"}, read_options});
4127+
for (auto const& row : rows1) {
4128+
(void)row;
4129+
}
4130+
EXPECT_THAT(txn1,
4131+
HasSessionAndTransaction("test-session-name", "txn1", false, ""));
4132+
}
4133+
4134+
TEST(ConnectionImplTest, ReadRequestLockHintShared) {
4135+
auto mock = std::make_shared<spanner_testing::MockSpannerStub>();
4136+
auto db = spanner::Database("project", "instance", "database");
4137+
EXPECT_CALL(*mock, BatchCreateSessions(_, _, HasDatabase(db)))
4138+
.WillOnce(Return(MakeSessionsResponse({"test-session-name"})));
4139+
EXPECT_CALL(*mock,
4140+
AsyncDeleteSession(_, _, _, HasSessionName("test-session-name")))
4141+
.WillOnce(Return(make_ready_future(Status{})));
4142+
Sequence s;
4143+
EXPECT_CALL(
4144+
*mock,
4145+
StreamingRead(
4146+
_, _,
4147+
AllOf(
4148+
HasSession("test-session-name"),
4149+
HasLockHint(google::spanner::v1::ReadRequest::LOCK_HINT_SHARED))))
4150+
.InSequence(s)
4151+
.WillOnce(Return(ByMove(MakeReader<google::spanner::v1::PartialResultSet>(
4152+
{R"pb(metadata: { transaction: { id: "txn1" } })pb"}))));
4153+
4154+
auto conn = MakeConnectionImpl(db, mock);
4155+
internal::OptionsSpan span(MakeLimitedTimeOptions());
4156+
spanner::ReadOptions read_options;
4157+
spanner::Transaction txn1 =
4158+
MakeReadOnlyTransaction(spanner::Transaction::ReadOnlyOptions());
4159+
auto read_params =
4160+
spanner::Connection::ReadParams{txn1,
4161+
"table",
4162+
spanner::KeySet::All(),
4163+
{"col"},
4164+
read_options,
4165+
absl::nullopt,
4166+
false,
4167+
spanner::DirectedReadOption::Type{},
4168+
spanner::LockHint::kLockHintShared};
4169+
auto rows1 = conn->Read(read_params);
4170+
for (auto const& row : rows1) {
4171+
(void)row;
4172+
}
4173+
EXPECT_THAT(txn1,
4174+
HasSessionAndTransaction("test-session-name", "txn1", false, ""));
4175+
}
4176+
40944177
TEST(ConnectionImplTest, OperationsFailOnInvalidatedTransaction) {
40954178
auto mock = std::make_shared<spanner_testing::MockSpannerStub>();
40964179
auto db = spanner::Database("placeholder_project", "placeholder_instance",

google/cloud/spanner/lock_hint.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_LOCK_HINT_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_LOCK_HINT_H
17+
18+
#include "google/cloud/spanner/version.h"
19+
20+
namespace google {
21+
namespace cloud {
22+
namespace spanner {
23+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
24+
25+
// A lock hint mechanism for reads done within a transaction.
26+
enum class LockHint {
27+
kLockHintUnspecified,
28+
kLockHintShared,
29+
kLockHintExclusive,
30+
};
31+
32+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
33+
} // namespace spanner
34+
} // namespace cloud
35+
} // namespace google
36+
37+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_LOCK_HINT_H

google/cloud/spanner/options.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "google/cloud/spanner/directed_read_replicas.h"
4343
#include "google/cloud/spanner/internal/session.h"
4444
#include "google/cloud/spanner/polling_policy.h"
45+
#include "google/cloud/spanner/lock_hint.h"
4546
#include "google/cloud/spanner/request_priority.h"
4647
#include "google/cloud/spanner/retry_policy.h"
4748
#include "google/cloud/spanner/version.h"
@@ -195,6 +196,15 @@ struct SessionPoolActionOnExhaustionOption {
195196
using Type = spanner::ActionOnExhaustion;
196197
};
197198

199+
/**
200+
* Option for `google::cloud::Options` to set the lock hint mechanism for reads done within a transaction
201+
*
202+
* @ingroup google-cloud-spanner-options
203+
*/
204+
struct LockHintOption {
205+
using Type = spanner::LockHint;
206+
};
207+
198208
/**
199209
* Option for `google::cloud::Options` to set the interval at which we refresh
200210
* sessions so they don't get collected by the backend GC.

google/cloud/spanner/read_partition.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ struct ReadPartitionInternals {
174174

175175
static spanner::Connection::ReadParams MakeReadParams(
176176
spanner::ReadPartition const& read_partition,
177-
spanner::DirectedReadOption::Type directed_read_option) {
177+
spanner::DirectedReadOption::Type directed_read_option, spanner::LockHint lock_hint) {
178178
return spanner::Connection::ReadParams{
179179
MakeTransactionFromIds(
180180
read_partition.SessionId(), read_partition.TransactionId(),
@@ -185,7 +185,8 @@ struct ReadPartitionInternals {
185185
read_partition.ReadOptions(),
186186
read_partition.PartitionToken(),
187187
read_partition.DataBoost(),
188-
std::move(directed_read_option)};
188+
std::move(directed_read_option),
189+
lock_hint};
189190
}
190191
};
191192

@@ -204,9 +205,9 @@ inline spanner::ReadPartition MakeReadPartition(
204205

205206
inline spanner::Connection::ReadParams MakeReadParams(
206207
spanner::ReadPartition const& read_partition,
207-
spanner::DirectedReadOption::Type directed_read_option) {
208+
spanner::DirectedReadOption::Type directed_read_option, spanner::LockHint lock_hint) {
208209
return ReadPartitionInternals::MakeReadParams(
209-
read_partition, std::move(directed_read_option));
210+
read_partition, std::move(directed_read_option), lock_hint);
210211
}
211212

212213
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END

0 commit comments

Comments
 (0)