diff --git a/google/cloud/bigtable/CMakeLists.txt b/google/cloud/bigtable/CMakeLists.txt index a47b8fc2f6853..e43bc7f4973c7 100644 --- a/google/cloud/bigtable/CMakeLists.txt +++ b/google/cloud/bigtable/CMakeLists.txt @@ -109,6 +109,8 @@ add_library( app_profile_config.cc app_profile_config.h async_row_reader.h + bound_query.cc + bound_query.h bytes.cc bytes.h cell.h @@ -236,6 +238,8 @@ add_library( options.h polling_policy.cc polling_policy.h + prepared_query.cc + prepared_query.h query_row.cc query_row.h read_modify_write_rule.h @@ -437,6 +441,7 @@ if (BUILD_TESTING) app_profile_config_test.cc async_read_stream_test.cc bigtable_version_test.cc + bound_query_test.cc bytes_test.cc cell_test.cc client_options_test.cc @@ -494,6 +499,7 @@ if (BUILD_TESTING) mutation_batcher_test.cc mutations_test.cc polling_policy_test.cc + prepared_query_test.cc query_row_test.cc read_modify_write_rule_test.cc row_range_test.cc diff --git a/google/cloud/bigtable/bigtable_client_unit_tests.bzl b/google/cloud/bigtable/bigtable_client_unit_tests.bzl index 5864c9b6628ce..7561f03849126 100644 --- a/google/cloud/bigtable/bigtable_client_unit_tests.bzl +++ b/google/cloud/bigtable/bigtable_client_unit_tests.bzl @@ -21,6 +21,7 @@ bigtable_client_unit_tests = [ "app_profile_config_test.cc", "async_read_stream_test.cc", "bigtable_version_test.cc", + "bound_query_test.cc", "bytes_test.cc", "cell_test.cc", "client_options_test.cc", @@ -78,6 +79,7 @@ bigtable_client_unit_tests = [ "mutation_batcher_test.cc", "mutations_test.cc", "polling_policy_test.cc", + "prepared_query_test.cc", "query_row_test.cc", "read_modify_write_rule_test.cc", "row_range_test.cc", diff --git a/google/cloud/bigtable/bound_query.cc b/google/cloud/bigtable/bound_query.cc new file mode 100644 index 0000000000000..00a7007d9d2b7 --- /dev/null +++ b/google/cloud/bigtable/bound_query.cc @@ -0,0 +1,56 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/bigtable/bound_query.h" + +namespace google { +namespace cloud { +namespace bigtable { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +StatusOr BoundQuery::prepared_query() const { + return query_plan_->prepared_query(); +} + +StatusOr BoundQuery::metadata() const { + return query_plan_->metadata(); +} + +std::unordered_map const& BoundQuery::parameters() const { + return parameters_; +} + +InstanceResource const& BoundQuery::instance() const { return instance_; } + +google::bigtable::v2::ExecuteQueryRequest BoundQuery::ToRequestProto() { + google::bigtable::v2::ExecuteQueryRequest result; + *result.mutable_instance_name() = instance_.FullName(); + auto prepared_query = query_plan_->prepared_query(); + if (prepared_query.ok()) { + *result.mutable_prepared_query() = query_plan_->prepared_query().value(); + } + + google::protobuf::Map parameters; + for (auto const& kv : parameters_) { + parameters[kv.first] = + bigtable_internal::ValueInternals::ToProto(kv.second).second; + } + *result.mutable_params() = parameters; + return result; +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace bigtable +} // namespace cloud +} // namespace google diff --git a/google/cloud/bigtable/bound_query.h b/google/cloud/bigtable/bound_query.h new file mode 100644 index 0000000000000..9d8ba8a0fdd87 --- /dev/null +++ b/google/cloud/bigtable/bound_query.h @@ -0,0 +1,72 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BOUND_QUERY_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BOUND_QUERY_H + +#include "google/cloud/bigtable/instance_resource.h" +#include "google/cloud/bigtable/internal/query_plan.h" +#include "google/cloud/bigtable/value.h" +#include "google/cloud/bigtable/version.h" +#include "google/cloud/completion_queue.h" +#include +#include +#include + +namespace google { +namespace cloud { +namespace bigtable { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +/** + * Move only type representing a PreparedQuery with parameter values. + * Created by calling PreparedQuery::BindParameters. + */ +class BoundQuery { + public: + // Copy and move. + BoundQuery(BoundQuery const&) = delete; + BoundQuery(BoundQuery&&) = default; + BoundQuery& operator=(BoundQuery const&) = delete; + BoundQuery& operator=(BoundQuery&&) = default; + + // Accessors + StatusOr prepared_query() const; + StatusOr metadata() const; + std::unordered_map const& parameters() const; + InstanceResource const& instance() const; + + google::bigtable::v2::ExecuteQueryRequest ToRequestProto(); + + private: + friend class PreparedQuery; + BoundQuery(InstanceResource instance, + std::shared_ptr query_plan, + std::unordered_map parameters) + : instance_(std::move(instance)), + query_plan_(std::move(query_plan)), + parameters_(std::move(parameters)) {} + + InstanceResource instance_; + // Copy of the query_plan_ contained by the PreparedQuery that created + // this BoundQuery. + std::shared_ptr query_plan_; + std::unordered_map parameters_; +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace bigtable +} // namespace cloud +} // namespace google +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BOUND_QUERY_H diff --git a/google/cloud/bigtable/bound_query_test.cc b/google/cloud/bigtable/bound_query_test.cc new file mode 100644 index 0000000000000..0607734708cfb --- /dev/null +++ b/google/cloud/bigtable/bound_query_test.cc @@ -0,0 +1,98 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/bigtable/bound_query.h" +#include "google/cloud/bigtable/prepared_query.h" +#include "google/cloud/bigtable/sql_statement.h" +#include "google/cloud/bigtable/value.h" +#include "google/cloud/testing_util/status_matchers.h" +#include + +namespace google { +namespace cloud { +namespace bigtable { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { +using ::google::bigtable::v2::PrepareQueryResponse; + +TEST(BoundQuery, FromPreparedQuery) { + CompletionQueue cq; + Project p("dummy-project"); + InstanceResource instance(p, "dummy-instance"); + std::string statement_contents( + "SELECT * FROM my_table WHERE col1 = @val1 and col2 = @val2;"); + SqlStatement sql_statement(statement_contents); + PrepareQueryResponse response; + std::unordered_map parameters = {{"val1", Value(true)}, + {"val2", Value(2.0)}}; + + // The following variables are only meant to confirm the metadata is correctly + // passed down to the BoundQuery. + auto metadata = std::make_unique(); + auto schema = std::make_unique(); + auto column = google::bigtable::v2::ColumnMetadata(); + *column.mutable_name() = "col1"; + schema->mutable_columns()->Add(std::move(column)); + metadata->set_allocated_proto_schema(schema.release()); + response.set_allocated_metadata(metadata.release()); + + PreparedQuery pq(cq, instance, sql_statement, response); + auto bq = pq.BindParameters(parameters); + EXPECT_EQ(instance.FullName(), bq.instance().FullName()); + EXPECT_STATUS_OK(bq.prepared_query()); + EXPECT_EQ(statement_contents, bq.prepared_query().value()); + EXPECT_EQ(parameters, bq.parameters()); + EXPECT_STATUS_OK(bq.metadata()); + EXPECT_TRUE(bq.metadata().value().has_proto_schema()); + EXPECT_EQ(1, bq.metadata().value().proto_schema().columns_size()); + EXPECT_EQ("col1", bq.metadata().value().proto_schema().columns()[0].name()); +} + +TEST(BoundQuery, ToRequestProto) { + CompletionQueue cq; + Project p("dummy-project"); + InstanceResource instance(p, "dummy-instance"); + std::string statement_contents( + "SELECT * FROM my_table WHERE col1 = @val1 and col2 = @val2;"); + SqlStatement sql_statement(statement_contents); + PrepareQueryResponse response; + std::unordered_map parameters = {{"val1", Value(true)}, + {"val2", Value(2.0)}}; + + PreparedQuery pq(cq, instance, sql_statement, response); + auto bq = pq.BindParameters(parameters); + google::bigtable::v2::ExecuteQueryRequest proto = bq.ToRequestProto(); + EXPECT_EQ(instance.FullName(), proto.instance_name()); + EXPECT_EQ(statement_contents, proto.prepared_query()); + + // Test param contents. + EXPECT_EQ(parameters.size(), proto.mutable_params()->size()); + + // The first parameter is a boolean. + EXPECT_TRUE(proto.params().contains("val1")); + auto val1 = proto.params().find("val1")->second; + EXPECT_TRUE(val1.has_bool_value()); + EXPECT_EQ(true, val1.bool_value()); + + // The second parameter is a double. + EXPECT_TRUE(proto.params().contains("val2")); + auto val2 = proto.params().find("val2")->second; + EXPECT_TRUE(val2.has_float_value()); + EXPECT_EQ(2.0, val2.float_value()); +} +} // namespace +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace bigtable +} // namespace cloud +} // namespace google diff --git a/google/cloud/bigtable/google_cloud_cpp_bigtable.bzl b/google/cloud/bigtable/google_cloud_cpp_bigtable.bzl index aec931b96bf9b..a108700cd49cb 100644 --- a/google/cloud/bigtable/google_cloud_cpp_bigtable.bzl +++ b/google/cloud/bigtable/google_cloud_cpp_bigtable.bzl @@ -48,6 +48,7 @@ google_cloud_cpp_bigtable_hdrs = [ "admin_client.h", "app_profile_config.h", "async_row_reader.h", + "bound_query.h", "bytes.h", "cell.h", "client_options.h", @@ -121,6 +122,7 @@ google_cloud_cpp_bigtable_hdrs = [ "mutations.h", "options.h", "polling_policy.h", + "prepared_query.h", "query_row.h", "read_modify_write_rule.h", "resource_names.h", @@ -173,6 +175,7 @@ google_cloud_cpp_bigtable_srcs = [ "admin/internal/bigtable_table_admin_tracing_stub.cc", "admin_client.cc", "app_profile_config.cc", + "bound_query.cc", "bytes.cc", "client_options.cc", "cluster_config.cc", @@ -227,6 +230,7 @@ google_cloud_cpp_bigtable_srcs = [ "mutation_batcher.cc", "mutations.cc", "polling_policy.cc", + "prepared_query.cc", "query_row.cc", "resource_names.cc", "row_range.cc", diff --git a/google/cloud/bigtable/prepared_query.cc b/google/cloud/bigtable/prepared_query.cc new file mode 100644 index 0000000000000..fae34f30d7ff1 --- /dev/null +++ b/google/cloud/bigtable/prepared_query.cc @@ -0,0 +1,36 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/bigtable/prepared_query.h" +#include "google/cloud/bigtable/sql_statement.h" + +namespace google { +namespace cloud { +namespace bigtable { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +BoundQuery PreparedQuery::BindParameters( + std::unordered_map params) const { + return BoundQuery(instance_, query_plan_, std::move(params)); +} + +InstanceResource const& PreparedQuery::instance() const { return instance_; } +SqlStatement const& PreparedQuery::sql_statement() const { + return sql_statement_; +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace bigtable +} // namespace cloud +} // namespace google diff --git a/google/cloud/bigtable/prepared_query.h b/google/cloud/bigtable/prepared_query.h new file mode 100644 index 0000000000000..b27a53acdef6e --- /dev/null +++ b/google/cloud/bigtable/prepared_query.h @@ -0,0 +1,75 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_PREPARED_QUERY_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_PREPARED_QUERY_H + +#include "google/cloud/bigtable/bound_query.h" +#include "google/cloud/bigtable/instance_resource.h" +#include "google/cloud/bigtable/internal/query_plan.h" +#include "google/cloud/bigtable/sql_statement.h" +#include "google/cloud/bigtable/value.h" +#include "google/cloud/bigtable/version.h" +#include "google/cloud/completion_queue.h" +#include +#include +#include + +namespace google { +namespace cloud { +namespace bigtable { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +// Represents a long-lived query execution plan. +// Query plans can expire and are refreshed as a background task. +class PreparedQuery { + public: + // Creates an instance of BoundQuery using the query plan ID from the + // response. + BoundQuery BindParameters( + std::unordered_map params) const; + + // Accessors + InstanceResource const& instance() const; + SqlStatement const& sql_statement() const; + + // While we work on the Bigtable GoogleSQL functionality, we will keep this + // constructor as public, but this will be converted to private as + // originally intended once other classes that orchestrate PreparedQuery + // are implemented. + PreparedQuery(CompletionQueue cq, InstanceResource instance, + SqlStatement sql_statement, + google::bigtable::v2::PrepareQueryResponse response) + : instance_(std::move(instance)), + sql_statement_(std::move(sql_statement)) { + *response.mutable_prepared_query() = sql_statement_.sql(); + + // For now, the refresh function has no effect, and we simply return a new + // prepared query response. + query_plan_ = bigtable_internal::QueryPlan::Create( + std::move(cq), std::move(response), + [] { return google::bigtable::v2::PrepareQueryResponse{}; }); + } + + private: + InstanceResource instance_; + SqlStatement sql_statement_; + std::shared_ptr query_plan_; +}; + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace bigtable +} // namespace cloud +} // namespace google +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_PREPARED_QUERY_H diff --git a/google/cloud/bigtable/prepared_query_test.cc b/google/cloud/bigtable/prepared_query_test.cc new file mode 100644 index 0000000000000..70b5e2e734d49 --- /dev/null +++ b/google/cloud/bigtable/prepared_query_test.cc @@ -0,0 +1,45 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "google/cloud/bigtable/prepared_query.h" +#include "google/cloud/bigtable/sql_statement.h" +#include "google/cloud/bigtable/value.h" +#include "google/cloud/testing_util/status_matchers.h" +#include + +namespace google { +namespace cloud { +namespace bigtable { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { +using ::google::bigtable::v2::PrepareQueryResponse; + +TEST(PreparedQuery, DefaultConstructor) { + CompletionQueue cq; + Project p("dummy-project"); + InstanceResource instance(p, "dummy-instance"); + std::string statement_contents( + "SELECT * FROM my_table WHERE col1 = @val1 and col2 = @val2;"); + SqlStatement sql_statement(statement_contents); + PrepareQueryResponse response; + PreparedQuery q(cq, instance, sql_statement, response); + EXPECT_EQ(instance.FullName(), q.instance().FullName()); + EXPECT_EQ(statement_contents, q.sql_statement().sql()); +} + +} // namespace +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace bigtable +} // namespace cloud +} // namespace google diff --git a/google/cloud/bigtable/sql_statement.h b/google/cloud/bigtable/sql_statement.h index ff5d4568630e3..0b184cddaeb79 100644 --- a/google/cloud/bigtable/sql_statement.h +++ b/google/cloud/bigtable/sql_statement.h @@ -1,4 +1,3 @@ - // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License");