Skip to content

Commit 286a6eb

Browse files
authored
feat(bigtable): reverse scans (#12022)
1 parent bfe5ef7 commit 286a6eb

7 files changed

Lines changed: 111 additions & 6 deletions

File tree

google/cloud/bigtable/data_connection.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,15 @@ future<std::vector<FailedMutation>> DataConnection::AsyncBulkApply(
7777
RowReader DataConnection::ReadRows(std::string const& table_name,
7878
RowSet row_set, std::int64_t rows_limit,
7979
Filter filter) {
80+
auto const& options = google::cloud::internal::CurrentOptions();
8081
return ReadRowsFull(ReadRowsParams{
8182
std::move(table_name),
82-
google::cloud::internal::CurrentOptions().get<AppProfileIdOption>(),
83-
std::move(row_set), rows_limit, std::move(filter)});
83+
options.get<AppProfileIdOption>(),
84+
std::move(row_set),
85+
rows_limit,
86+
std::move(filter),
87+
options.get<ReverseScanOption>(),
88+
});
8489
}
8590

8691
// NOLINTNEXTLINE(performance-unnecessary-value-param)

google/cloud/bigtable/data_connection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ struct ReadRowsParams {
4949
RowSet row_set;
5050
std::int64_t rows_limit;
5151
Filter filter = Filter::PassAllFilter();
52+
bool reverse = false;
5253
};
5354

5455
/**

google/cloud/bigtable/examples/read_snippets.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,27 @@ void ReadFilter(google::cloud::bigtable::Table table,
334334
(std::move(table));
335335
}
336336

337+
void ReadRowsReverse(google::cloud::bigtable::Table table,
338+
std::vector<std::string> const&) {
339+
//! [reverse scan] [START bigtable_reverse_scan]
340+
namespace cbt = ::google::cloud::bigtable;
341+
using ::google::cloud::Options;
342+
using ::google::cloud::StatusOr;
343+
[](cbt::Table table) {
344+
// Read and print the rows.
345+
auto reader = table.ReadRows(
346+
cbt::RowRange::RightOpen("phone#5c10102", "phone#5c10103"), 3,
347+
cbt::Filter::PassAllFilter(),
348+
Options{}.set<cbt::ReverseScanOption>(true));
349+
for (StatusOr<cbt::Row>& row : reader) {
350+
if (!row) throw std::move(row).status();
351+
PrintRow(*row);
352+
}
353+
}
354+
//! [reverse scan] [END bigtable_reverse_scan]
355+
(std::move(table));
356+
}
357+
337358
void RunAll(std::vector<std::string> const& argv) {
338359
namespace examples = ::google::cloud::bigtable::examples;
339360
namespace cbt = ::google::cloud::bigtable;
@@ -391,6 +412,10 @@ void RunAll(std::vector<std::string> const& argv) {
391412
ReadFilter(table, {});
392413
std::cout << "Running ReadRowsWithLimit() example" << std::endl;
393414
ReadRowsWithLimit(table, {"5"});
415+
if (!google::cloud::bigtable::examples::UsingEmulator()) {
416+
std::cout << "Running ReadRowsReverse() example" << std::endl;
417+
ReadRowsReverse(table, {});
418+
}
394419

395420
std::cout << "Running ReadKeySet() example" << std::endl;
396421
ReadKeysSet({table.project_id(), table.instance_id(), table.table_id(),
@@ -415,6 +440,7 @@ int main(int argc, char* argv[]) try {
415440
MakeCommandEntry("read-row-ranges", {}, ReadRowRanges),
416441
MakeCommandEntry("read-row-prefix", {}, ReadRowPrefix),
417442
MakeCommandEntry("read-filter", {}, ReadFilter),
443+
MakeCommandEntry("read-rows-reverse", {}, ReadRowsReverse),
418444
{"auto", RunAll},
419445
};
420446

google/cloud/bigtable/internal/data_connection_impl.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ bigtable::RowReader DataConnectionImpl::ReadRowsFull(
178178
auto impl = std::make_shared<DefaultRowReader>(
179179
stub_, std::move(params.app_profile_id), std::move(params.table_name),
180180
std::move(params.row_set), params.rows_limit, std::move(params.filter),
181-
false, retry_policy(), backoff_policy());
181+
params.reverse, retry_policy(), backoff_policy());
182182
return MakeRowReader(std::move(impl));
183183
}
184184

@@ -374,7 +374,7 @@ void DataConnectionImpl::AsyncReadRows(
374374
std::function<future<bool>(bigtable::Row)> on_row,
375375
std::function<void(Status)> on_finish, bigtable::RowSet row_set,
376376
std::int64_t rows_limit, bigtable::Filter filter) {
377-
auto reverse = false;
377+
auto reverse = internal::CurrentOptions().get<bigtable::ReverseScanOption>();
378378
bigtable_internal::AsyncRowReader::Create(
379379
background_->cq(), stub_, app_profile_id(), table_name, std::move(on_row),
380380
std::move(on_finish), std::move(row_set), rows_limit, std::move(filter),

google/cloud/bigtable/internal/data_connection_impl_test.cc

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "google/cloud/bigtable/internal/data_connection_impl.h"
1616
#include "google/cloud/bigtable/data_connection.h"
1717
#include "google/cloud/bigtable/internal/defaults.h"
18+
#include "google/cloud/bigtable/options.h"
1819
#include "google/cloud/bigtable/testing/mock_bigtable_stub.h"
1920
#include "google/cloud/bigtable/testing/mock_policies.h"
2021
#include "google/cloud/common_options.h"
@@ -40,6 +41,7 @@ using ::google::cloud::bigtable::DataBackoffPolicyOption;
4041
using ::google::cloud::bigtable::DataLimitedErrorCountRetryPolicy;
4142
using ::google::cloud::bigtable::DataRetryPolicyOption;
4243
using ::google::cloud::bigtable::IdempotentMutationPolicyOption;
44+
using ::google::cloud::bigtable::ReverseScanOption;
4345
using ::google::cloud::bigtable::testing::MockAsyncReadRowsStream;
4446
using ::google::cloud::bigtable::testing::MockBigtableStub;
4547
using ::google::cloud::bigtable::testing::MockIdempotentMutationPolicy;
@@ -705,6 +707,23 @@ TEST(DataConnectionTest, ReadRows) {
705707
EXPECT_EQ(reader.begin(), reader.end());
706708
}
707709

710+
TEST(DataConnectionTest, ReadRowsReverseScan) {
711+
auto mock = std::make_shared<MockBigtableStub>();
712+
EXPECT_CALL(*mock, ReadRows)
713+
.WillOnce([](auto, google::bigtable::v2::ReadRowsRequest const& request) {
714+
EXPECT_TRUE(request.reversed());
715+
716+
auto stream = std::make_unique<MockReadRowsStream>();
717+
EXPECT_CALL(*stream, Read).WillOnce(Return(Status()));
718+
return stream;
719+
});
720+
721+
auto conn = TestConnection(std::move(mock));
722+
internal::OptionsSpan span(CallOptions().set<ReverseScanOption>(true));
723+
auto reader = conn->ReadRows(kTableName, TestRowSet(), 42, TestFilter());
724+
EXPECT_EQ(reader.begin(), reader.end());
725+
}
726+
708727
// The DefaultRowReader is tested extensively in `default_row_reader_test.cc`.
709728
// In this test, we just verify that the configuration is passed along.
710729
TEST(DataConnectionTest, ReadRowsFull) {
@@ -716,7 +735,7 @@ TEST(DataConnectionTest, ReadRowsFull) {
716735
EXPECT_EQ(42, request.rows_limit());
717736
EXPECT_THAT(request, HasTestRowSet());
718737
EXPECT_THAT(request.filter(), IsTestFilter());
719-
EXPECT_FALSE(request.reversed());
738+
EXPECT_TRUE(request.reversed());
720739

721740
auto stream = std::make_unique<MockReadRowsStream>();
722741
EXPECT_CALL(*stream, Read).WillOnce(Return(Status()));
@@ -726,7 +745,7 @@ TEST(DataConnectionTest, ReadRowsFull) {
726745
auto conn = TestConnection(std::move(mock));
727746
internal::OptionsSpan span(CallOptions());
728747
auto reader = conn->ReadRowsFull(bigtable::ReadRowsParams{
729-
kTableName, kAppProfile, TestRowSet(), 42, TestFilter()});
748+
kTableName, kAppProfile, TestRowSet(), 42, TestFilter(), true});
730749
EXPECT_EQ(reader.begin(), reader.end());
731750
}
732751

@@ -1478,6 +1497,32 @@ TEST(DataConnectionTest, AsyncReadRows) {
14781497
TestFilter());
14791498
}
14801499

1500+
TEST(DataConnectionTest, AsyncReadRowsReverseScan) {
1501+
auto mock = std::make_shared<MockBigtableStub>();
1502+
EXPECT_CALL(*mock, AsyncReadRows)
1503+
.WillOnce(
1504+
[](CompletionQueue const&, auto, v2::ReadRowsRequest const& request) {
1505+
EXPECT_TRUE(request.reversed());
1506+
using ErrorStream =
1507+
internal::AsyncStreamingReadRpcError<v2::ReadRowsResponse>;
1508+
return std::make_unique<ErrorStream>(PermanentError());
1509+
});
1510+
1511+
MockFunction<future<bool>(bigtable::Row const&)> on_row;
1512+
EXPECT_CALL(on_row, Call).Times(0);
1513+
1514+
MockFunction<void(Status)> on_finish;
1515+
EXPECT_CALL(on_finish, Call).WillOnce([](Status const& status) {
1516+
EXPECT_THAT(status, StatusIs(StatusCode::kPermissionDenied));
1517+
});
1518+
1519+
auto conn = TestConnection(std::move(mock));
1520+
internal::OptionsSpan span(CallOptions().set<ReverseScanOption>(true));
1521+
conn->AsyncReadRows(kTableName, on_row.AsStdFunction(),
1522+
on_finish.AsStdFunction(), TestRowSet(), 42,
1523+
TestFilter());
1524+
}
1525+
14811526
TEST(DataConnectionTest, AsyncReadRowEmpty) {
14821527
auto mock = std::make_shared<MockBigtableStub>();
14831528
EXPECT_CALL(*mock, AsyncReadRows)

google/cloud/bigtable/options.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,28 @@ struct AppProfileIdOption {
7676
using Type = std::string;
7777
};
7878

79+
/**
80+
* Read rows in reverse order.
81+
*
82+
* The rows will be streamed in reverse lexicographic order of the keys. This is
83+
* particularly useful to get the last N records before a key.
84+
*
85+
* This option does not affect the contents of the rows, just the order that
86+
* the rows are returned.
87+
*
88+
* @note When using this option, the order of row keys in a `bigtable::RowRange`
89+
* does not change. The row keys still must be supplied in lexicographic order.
90+
*
91+
* @snippet read_snippets.cc reverse scan
92+
*
93+
* @see https://cloud.google.com/bigtable/docs/reads#reverse-scan
94+
*
95+
* @ingroup bigtable-options
96+
*/
97+
struct ReverseScanOption {
98+
using Type = bool;
99+
};
100+
79101
/**
80102
* The endpoint for data operations.
81103
*

google/cloud/bigtable/tests/data_integration_test.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ TEST_P(DataIntegrationTest, TableReadRowsAllRows) {
198198

199199
auto read4 = table.ReadRows(RowSet(), Filter::PassAllFilter());
200200
CheckEqualUnordered(created, MoveCellsFromReader(read4));
201+
202+
if (GetParam() == "with-data-connection" && !UsingCloudBigtableEmulator()) {
203+
auto read5 = table.ReadRows(RowSet(), Filter::PassAllFilter(),
204+
Options{}.set<ReverseScanOption>(true));
205+
CheckEqualUnordered(created, MoveCellsFromReader(read5));
206+
}
201207
}
202208

203209
TEST_P(DataIntegrationTest, TableReadRowsPartialRows) {

0 commit comments

Comments
 (0)