@@ -40,6 +40,7 @@ using ::std::chrono::milliseconds;
4040using ::testing::Contains;
4141using ::testing::ElementsAre;
4242using ::testing::HasSubstr;
43+ using ::testing::UnorderedElementsAre;
4344using ms = std::chrono::milliseconds;
4445
4546class DataIntegrationTest : public TableIntegrationTest ,
@@ -841,6 +842,70 @@ TEST_P(DataIntegrationTest, SingleColumnQueryWithHistory) {
841842 EXPECT_EQ (std::get<1 >(entry1), value_old);
842843}
843844
845+ TEST_P (DataIntegrationTest, QueryWithNulls) {
846+ if (UsingCloudBigtableEmulator ()) GTEST_SKIP ();
847+ auto const table_id = testing::TableTestEnvironment::table_id ();
848+ auto retry_policy_option = DataLimitedErrorCountRetryPolicy (0 ).clone ();
849+ auto backoff_policy_option =
850+ google::cloud::internal::ExponentialBackoffPolicy (ms (0 ), ms (0 ), 2.0 )
851+ .clone ();
852+ auto query_refresh_option =
853+ bigtable::experimental::QueryPlanRefreshLimitedErrorCountRetryPolicy (0 )
854+ .clone ();
855+ auto opts =
856+ Options{}
857+ .set <DataRetryPolicyOption>(std::move (retry_policy_option))
858+ .set <DataBackoffPolicyOption>(std::move (backoff_policy_option))
859+ .set <bigtable::experimental::QueryPlanRefreshRetryPolicyOption>(
860+ std::move (query_refresh_option));
861+ auto connection = google::cloud::bigtable::MakeDataConnection (opts);
862+ auto table =
863+ Table (connection, TableResource (project_id (), instance_id (), table_id));
864+ std::string const row_key1 = " query-with-nulls-row-1" ;
865+ std::string const row_key2 = " query-with-nulls-row-2" ;
866+ std::string const row_key3 = " query-with-nulls-row-3" ;
867+ std::string const family = kFamily4 ;
868+ std::string const column1 = " c1" ;
869+ std::string const value1 = " v1" ;
870+ std::string const value3 = " v3" ;
871+
872+ std::vector<Cell> created = {
873+ {row_key1, family, column1, 0 , value1},
874+ {row_key3, family, column1, 0 , value3},
875+ };
876+ BulkApply (table, created);
877+
878+ auto client = Client (connection, opts);
879+ std::vector<std::string> full_table_path =
880+ absl::StrSplit (table.table_name (), ' /' );
881+ auto table_name = full_table_path.back ();
882+ std::string quoted_table_name = " `" + table_name + " `" ;
883+ Project project (project_id ());
884+ InstanceResource instance_resource (project, instance_id ());
885+ auto prepared_query = client.PrepareQuery (
886+ instance_resource,
887+ SqlStatement (" SELECT CAST(_key AS STRING) AS _key, "
888+ " CAST(family4['c1'] AS STRING) AS c1 FROM " +
889+ quoted_table_name + " WHERE _key IN ('" + row_key1 + " ', '" +
890+ row_key2 + " ', '" + row_key3 + " ')" ));
891+ ASSERT_STATUS_OK (prepared_query);
892+ auto bound_query = prepared_query->BindParameters ({});
893+ auto row_stream = client.ExecuteQuery (std::move (bound_query));
894+
895+ using RowType = std::tuple<std::string, absl::optional<std::string>>;
896+ std::vector<RowType> actual_rows;
897+ for (auto & row : StreamOf<RowType>(row_stream)) {
898+ ASSERT_STATUS_OK (row);
899+ actual_rows.push_back (*std::move (row));
900+ }
901+ EXPECT_EQ (actual_rows.size (), 2 );
902+ EXPECT_THAT (
903+ actual_rows,
904+ UnorderedElementsAre (
905+ std::make_tuple (row_key1, absl::optional<std::string>(value1)),
906+ std::make_tuple (row_key3, absl::optional<std::string>(value3))));
907+ }
908+
844909// TODO(#8800) - remove after deprecation is complete
845910#include " google/cloud/internal/diagnostics_pop.inc"
846911
0 commit comments