Skip to content

Commit 41b9672

Browse files
feat(bigtable): add minimal query client (#15659)
* feat(bigtable): add minimal query client Relies on data_connection unimplemented methods. Here we lay out the basic structure of the client * chore: adapt conn_impl test * chore: make methods non-const * chore: return RowStream directly * chore: pass arguments to PrepareQuery by reference * chore: remove const reference from params struct * chore: fix clang-tidy errors * feat(bigtable): add minimal query client Relies on data_connection unimplemented methods. Here we lay out the basic structure of the client * chore: adapt conn_impl test * chore: make methods non-const * chore: return RowStream directly * chore: pass arguments to PrepareQuery by reference * chore: remove const reference from params struct * chore: fix clang-tidy errors * chore: remove const& from Options in execute query * chore: adapt tests * chore: post-merge
1 parent f1bc406 commit 41b9672

File tree

10 files changed

+315
-3
lines changed

10 files changed

+315
-3
lines changed

google/cloud/bigtable/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ add_library(
114114
bytes.cc
115115
bytes.h
116116
cell.h
117+
client.cc
118+
client.h
117119
client_options.cc
118120
client_options.h
119121
cluster_config.cc
@@ -445,6 +447,7 @@ if (BUILD_TESTING)
445447
bytes_test.cc
446448
cell_test.cc
447449
client_options_test.cc
450+
client_test.cc
448451
cluster_config_test.cc
449452
column_family_test.cc
450453
data_client_test.cc

google/cloud/bigtable/bigtable_client_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ bigtable_client_unit_tests = [
2525
"bytes_test.cc",
2626
"cell_test.cc",
2727
"client_options_test.cc",
28+
"client_test.cc",
2829
"cluster_config_test.cc",
2930
"column_family_test.cc",
3031
"data_client_test.cc",

google/cloud/bigtable/client.cc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
#include "google/cloud/bigtable/client.h"
16+
#include "internal/partial_result_set_source.h"
17+
18+
namespace google {
19+
namespace cloud {
20+
namespace bigtable {
21+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
22+
23+
StatusOr<PreparedQuery> Client::PrepareQuery(
24+
InstanceResource const& instance, SqlStatement const& statement,
25+
// NOLINTNEXTLINE(performance-unnecessary-value-param)
26+
Options) {
27+
PrepareQueryParams params{std::move(instance), std::move(statement)};
28+
return conn_->PrepareQuery(std::move(params));
29+
}
30+
31+
future<StatusOr<PreparedQuery>> Client::AsyncPrepareQuery(
32+
// NOLINTNEXTLINE(performance-unnecessary-value-param)
33+
InstanceResource const& instance, SqlStatement const& statement, Options) {
34+
PrepareQueryParams params{std::move(instance), std::move(statement)};
35+
return conn_->AsyncPrepareQuery(std::move(params));
36+
}
37+
38+
// NOLINTNEXTLINE(performance-unnecessary-value-param)
39+
RowStream Client::ExecuteQuery(BoundQuery&& bound_query, Options) {
40+
ExecuteQueryParams params{std::move(bound_query)};
41+
auto row_stream = conn_->ExecuteQuery(params);
42+
if (!row_stream.ok()) {
43+
return RowStream(
44+
std::make_unique<bigtable_internal::StatusOnlyResultSetSource>(
45+
row_stream.status()));
46+
}
47+
return std::move(row_stream.value());
48+
}
49+
50+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
51+
} // namespace bigtable
52+
} // namespace cloud
53+
} // namespace google

google/cloud/bigtable/client.h

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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_BIGTABLE_CLIENT_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_CLIENT_H
17+
18+
#include "google/cloud/bigtable/data_connection.h"
19+
20+
namespace google {
21+
namespace cloud {
22+
namespace bigtable {
23+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
24+
25+
/**
26+
* Connects to Cloud Bigtable's query preparation and execution APIs.
27+
*
28+
* A Bigtable query's lifecycle consists of two phases:
29+
* 1. Preparing a query: The service creates and caches a query execution plan.
30+
* 2. Executing a query: The client sends the plan ID and concrete parameters
31+
* to the service, which then executes the query.
32+
*
33+
* This class provides methods for both preparing and executing SQL queries.
34+
*
35+
* @par Cost
36+
* Creating a `Client` object is a relatively low-cost operation. It does not
37+
* require connecting to the Bigtable servers. However, each `Client` object
38+
* holds a `std::shared_ptr<DataConnection>`, and the first RPC made on this
39+
* connection may incur a higher latency as the connection is established.
40+
* For this reason, it is recommended to reuse `Client` objects when possible.
41+
*
42+
* @par Example
43+
* @code
44+
* #include "google/cloud/bigtable/client.h"
45+
* #include "google/cloud/project.h"
46+
* #include <iostream>
47+
*
48+
* int main() {
49+
* namespace cbt = google::cloud::bigtable;
50+
* cbt::Client client(cbt::MakeDataConnection());
51+
* cbt::InstanceResource instance(google::cloud::Project("my-project"),
52+
* "my-instance");
53+
*
54+
* // Declare a parameter with a type, but no value.
55+
* cbt::SqlStatement statement(
56+
* "SELECT _key, CAST(family['qual'] AS STRING) AS value "
57+
* "FROM my-table WHERE _key = @key",
58+
* {{"key", cbt::Value(cbt::Bytes())}});
59+
*
60+
* google::cloud::StatusOr<cbt::PreparedQuery> prepared_query =
61+
* client.PrepareQuery(instance, statement);
62+
* if (!prepared_query) throw std::move(prepared_query).status();
63+
*
64+
* auto bound_query = prepared_query->BindParameters(
65+
* {{"key", cbt::Value("row-key-2")}});
66+
*
67+
* RowStream results =
68+
* client.ExecuteQuery(std::move(bound_query));
69+
*
70+
* ... // process rows
71+
* }
72+
* @endcode
73+
*/
74+
class Client {
75+
public:
76+
/**
77+
* Creates a new Client object.
78+
*
79+
* @param conn The connection object to use for all RPCs. This is typically
80+
* created by `MakeDataConnection()`.
81+
* @param opts Unused for now
82+
*/
83+
explicit Client(std::shared_ptr<DataConnection> conn, Options opts = {})
84+
: conn_(std::move(conn)), opts_(std::move(opts)) {}
85+
86+
/**
87+
* Prepares a query for future execution.
88+
*
89+
* This sends the SQL statement to the service, which validates it and
90+
* creates an execution plan, returning a handle to this plan.
91+
*
92+
* @param instance The instance to prepare the query against.
93+
* @param statement The SQL statement to prepare.
94+
* @param opts Unused for now
95+
* @return A `StatusOr` containing the prepared query on success. On
96+
* failure, the `Status` contains error details.
97+
*/
98+
StatusOr<PreparedQuery> PrepareQuery(InstanceResource const& instance,
99+
SqlStatement const& statement,
100+
Options opts = {});
101+
102+
/**
103+
* Asynchronously prepares a query for future execution.
104+
*
105+
* This sends the SQL statement to the service, which validates it and
106+
* creates an execution plan, returning a handle to this plan.
107+
*
108+
* @param instance The instance to prepare the query against.
109+
* @param statement The SQL statement to prepare.
110+
* @param opts Unused for now
111+
* @return A `future` that will be satisfied with a `StatusOr` containing
112+
* the prepared query on success. On failure, the `Status` will contain
113+
* error details.
114+
*/
115+
future<StatusOr<PreparedQuery>> AsyncPrepareQuery(
116+
InstanceResource const& instance, SqlStatement const& statement,
117+
Options opts = {});
118+
119+
/**
120+
* Executes a bound query with concrete parameters.
121+
*
122+
* This returns a `RowStream`, which is a range of `StatusOr<QueryRow>`.
123+
* The `BoundQuery` is passed by rvalue-reference to promote thread safety,
124+
* as it is not safe to use a `BoundQuery` concurrently.
125+
*
126+
* @param bound_query The bound query to execute.
127+
* @param opts Overrides the client-level options for this call.
128+
* @return A `RowStream` that can be used to iterate over the result rows.
129+
*/
130+
RowStream ExecuteQuery(BoundQuery&& bound_query, Options opts = {});
131+
132+
private:
133+
std::shared_ptr<DataConnection> conn_;
134+
Options opts_;
135+
};
136+
137+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
138+
} // namespace bigtable
139+
} // namespace cloud
140+
} // namespace google
141+
142+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_CLIENT_H
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
#include "google/cloud/bigtable/client.h"
16+
#include "google/cloud/testing_util/status_matchers.h"
17+
#include "mocks/mock_data_connection.h"
18+
19+
namespace google {
20+
namespace cloud {
21+
namespace bigtable {
22+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
23+
namespace {
24+
25+
using ::google::bigtable::v2::PrepareQueryResponse;
26+
using ::google::cloud::testing_util::StatusIs;
27+
28+
TEST(Client, PrepareQuery) {
29+
auto conn_mock = std::make_shared<bigtable_mocks::MockDataConnection>();
30+
InstanceResource instance(Project("the-project"), "the-instance");
31+
SqlStatement sql("SELECT * FROM the-table");
32+
EXPECT_CALL(*conn_mock, PrepareQuery)
33+
.WillOnce([](PrepareQueryParams const& params) {
34+
EXPECT_EQ("projects/the-project/instances/the-instance",
35+
params.instance.FullName());
36+
EXPECT_EQ("SELECT * FROM the-table", params.sql_statement.sql());
37+
PreparedQuery q(CompletionQueue{}, params.instance,
38+
params.sql_statement, PrepareQueryResponse{});
39+
return q;
40+
});
41+
42+
Client client(std::move(conn_mock));
43+
auto prepared_query = client.PrepareQuery(instance, sql);
44+
ASSERT_STATUS_OK(prepared_query);
45+
}
46+
47+
TEST(Client, AsyncPrepareQuery) {
48+
auto conn = MakeDataConnection();
49+
Client client(conn);
50+
InstanceResource instance(Project("test-project"), "test-instance");
51+
SqlStatement sql("SELECT * FROM `test-table`");
52+
auto prepared_query = client.AsyncPrepareQuery(instance, sql);
53+
EXPECT_THAT(prepared_query.get().status(),
54+
StatusIs(StatusCode::kUnimplemented, "not implemented"));
55+
}
56+
57+
TEST(Client, ExecuteQuery) {
58+
auto conn = MakeDataConnection();
59+
Client client(conn);
60+
InstanceResource instance(Project("test-project"), "test-instance");
61+
SqlStatement sql("SELECT * FROM `test-table`");
62+
auto prepared_query =
63+
PreparedQuery(CompletionQueue{}, instance, sql, PrepareQueryResponse{});
64+
auto bound_query = prepared_query.BindParameters({});
65+
auto row_stream = client.ExecuteQuery(std::move(bound_query));
66+
// We expect a row stream with a single unimplemented status row while
67+
// this is not implemented.
68+
for (auto const& row : row_stream) {
69+
EXPECT_THAT(row.status(),
70+
StatusIs(StatusCode::kUnimplemented, "not implemented"));
71+
}
72+
EXPECT_EQ(1, std::distance(row_stream.begin(), row_stream.end()));
73+
}
74+
75+
} // namespace
76+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
77+
} // namespace bigtable
78+
} // namespace cloud
79+
} // namespace google

google/cloud/bigtable/data_connection.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ struct PrepareQueryParams {
6060
bigtable::SqlStatement sql_statement;
6161
};
6262

63-
struct ExecuteQueryParams {};
63+
struct ExecuteQueryParams {
64+
bigtable::BoundQuery bound_query;
65+
};
6466

6567
/**
6668
* A connection to the Cloud Bigtable Data API.

google/cloud/bigtable/google_cloud_cpp_bigtable.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ google_cloud_cpp_bigtable_hdrs = [
5151
"bound_query.h",
5252
"bytes.h",
5353
"cell.h",
54+
"client.h",
5455
"client_options.h",
5556
"cluster_config.h",
5657
"cluster_list_responses.h",
@@ -177,6 +178,7 @@ google_cloud_cpp_bigtable_srcs = [
177178
"app_profile_config.cc",
178179
"bound_query.cc",
179180
"bytes.cc",
181+
"client.cc",
180182
"client_options.cc",
181183
"cluster_config.cc",
182184
"data_client.cc",

google/cloud/bigtable/internal/data_connection_impl_test.cc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
#include "google/cloud/bigtable/internal/metrics.h"
2020
#include "google/cloud/testing_util/fake_clock.h"
2121
#endif
22+
#include "google/cloud/bigtable/instance_resource.h"
2223
#include "google/cloud/bigtable/options.h"
24+
#include "google/cloud/bigtable/prepared_query.h"
2325
#include "google/cloud/bigtable/sql_statement.h"
2426
#include "google/cloud/bigtable/testing/mock_bigtable_stub.h"
2527
#include "google/cloud/bigtable/testing/mock_mutate_rows_limiter.h"
@@ -2726,8 +2728,15 @@ TEST_F(DataConnectionTest, AsyncReadRowFailure) {
27262728
TEST_F(DataConnectionTest, ExecuteQuery) {
27272729
auto conn = TestConnection(std::make_shared<MockBigtableStub>());
27282730
internal::OptionsSpan span(CallOptions());
2729-
EXPECT_THAT(conn->ExecuteQuery(bigtable::ExecuteQueryParams{}),
2730-
StatusIs(StatusCode::kUnimplemented));
2731+
bigtable::InstanceResource instance(Project("test-project"), "test-instance");
2732+
bigtable::SqlStatement sql("SELECT * FROM `test-table`");
2733+
auto prepared_query =
2734+
bigtable::PreparedQuery(CompletionQueue{}, instance, sql,
2735+
google::bigtable::v2::PrepareQueryResponse{});
2736+
auto bound_query = prepared_query.BindParameters({});
2737+
EXPECT_THAT(
2738+
conn->ExecuteQuery(bigtable::ExecuteQueryParams{std::move(bound_query)}),
2739+
StatusIs(StatusCode::kUnimplemented));
27312740
}
27322741

27332742
TEST_F(DataConnectionTest, PrepareQuerySuccess) {

google/cloud/bigtable/internal/partial_result_set_source.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,24 @@ class PartialResultSetSource : public bigtable::ResultSourceInterface {
9797
State state_ = State::kReading;
9898
};
9999

100+
/**
101+
* Helper class that represents a RowStream with non-ok status.
102+
* When iterated over, it will produce a single StatusOr<>.
103+
*/
104+
class StatusOnlyResultSetSource : public bigtable::ResultSourceInterface {
105+
public:
106+
explicit StatusOnlyResultSetSource(Status status)
107+
: status_(std::move(status)) {}
108+
109+
StatusOr<bigtable::QueryRow> NextRow() override { return status_; }
110+
absl::optional<google::bigtable::v2::ResultSetMetadata> Metadata() override {
111+
return {};
112+
}
113+
114+
private:
115+
Status status_;
116+
};
117+
100118
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
101119
} // namespace bigtable_internal
102120
} // namespace cloud

google/cloud/bigtable/mocks/mock_data_connection.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class MockDataConnection : public bigtable::DataConnection {
122122
(std::string const& table_name, std::string row_key,
123123
bigtable::Filter filter),
124124
(override));
125+
126+
MOCK_METHOD(StatusOr<bigtable::PreparedQuery>, PrepareQuery,
127+
(bigtable::PrepareQueryParams const& params), (override));
125128
};
126129

127130
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END

0 commit comments

Comments
 (0)