@@ -648,6 +648,7 @@ TEST_P(DataIntegrationTest, ClientQueryColumnFamily) {
648648 Project project (project_id ());
649649 InstanceResource instance_resource (project, instance_id ());
650650
651+
651652 auto prepared_query = client.PrepareQuery (
652653 instance_resource,
653654 SqlStatement (" SELECT family4 AS c0 FROM " + quoted_table_name +
@@ -672,6 +673,138 @@ TEST_P(DataIntegrationTest, ClientQueryColumnFamily) {
672673 ASSERT_EQ (row1.values ().at (1 ), Value (value2));
673674}
674675
676+ TEST_P (DataIntegrationTest, ClientQueryColumnFamilyWithHistory) {
677+ if (UsingCloudBigtableEmulator ()) GTEST_SKIP ();
678+ auto const table_id = testing::TableTestEnvironment::table_id ();
679+ auto retry_policy_option = DataLimitedErrorCountRetryPolicy (0 ).clone ();
680+ auto backoff_policy_option =
681+ google::cloud::internal::ExponentialBackoffPolicy (ms (0 ), ms (0 ), 2.0 )
682+ .clone ();
683+ auto query_refresh_option =
684+ bigtable::experimental::QueryPlanRefreshLimitedErrorCountRetryPolicy (0 )
685+ .clone ();
686+ auto opts =
687+ Options{}
688+ .set <DataRetryPolicyOption>(std::move (retry_policy_option))
689+ .set <DataBackoffPolicyOption>(std::move (backoff_policy_option))
690+ .set <bigtable::experimental::QueryPlanRefreshRetryPolicyOption>(
691+ std::move (query_refresh_option));
692+ auto connection = google::cloud::bigtable::MakeDataConnection (opts);
693+ auto table =
694+ Table (connection, TableResource (project_id (), instance_id (), table_id));
695+ std::string const row_key = " row-key-for-history-test" ;
696+ std::string const family = kFamily4 ;
697+ std::string const column1 = " c1" ;
698+ std::string const column2 = " c2" ;
699+ std::string const column_1_value_old = " c1_v1_old" ;
700+ std::string const column_1_value_new = " c1_v2_new" ;
701+ std::string const column_2_value_old = " c2_v1_old" ;
702+ std::string const column_2_value_new = " c2_v2_new" ;
703+
704+ // Get times in microseconds
705+ auto now_sys = std::chrono::system_clock::now ();
706+ auto current_time = std::chrono::duration_cast<std::chrono::microseconds>(
707+ now_sys.time_since_epoch ())
708+ .count ();
709+ auto old_time = current_time - 5000000 ; // 5 seconds older
710+
711+ // Apply mutations with specific timestamps
712+ SingleRowMutation mutation (row_key);
713+ mutation.emplace_back (
714+ SetCell (family, column1,
715+ duration_cast<milliseconds>(std::chrono::microseconds (old_time)),
716+ column_1_value_old));
717+ mutation.emplace_back (SetCell (
718+ family, column1,
719+ duration_cast<milliseconds>(std::chrono::microseconds (current_time)),
720+ column_1_value_new));
721+ mutation.emplace_back (
722+ SetCell (family, column2,
723+ duration_cast<milliseconds>(std::chrono::microseconds (old_time)),
724+ column_2_value_old));
725+ mutation.emplace_back (SetCell (
726+ family, column2,
727+ duration_cast<milliseconds>(std::chrono::microseconds (current_time)),
728+ column_2_value_new));
729+ auto apply_status = table.Apply (std::move (mutation));
730+ ASSERT_TRUE (apply_status.ok ()) << apply_status.message ();
731+
732+ // Execute query using WITH_HISTORY
733+ auto client = Client (connection, opts);
734+ std::vector<std::string> full_table_path =
735+ absl::StrSplit (table.table_name (), ' /' );
736+ auto table_name = full_table_path.back ();
737+ std::string quoted_table_name = " `" + table_name + " `" ;
738+ Project project (project_id ());
739+ InstanceResource instance_resource (project, instance_id ());
740+ std::string query_string = absl::StrFormat (
741+ R"sql( SELECT family4 AS family4_history
742+ FROM %s(WITH_HISTORY => TRUE)
743+ WHERE _key = '%s')sql" ,
744+ quoted_table_name, row_key);
745+ auto prepared_query =
746+ client.PrepareQuery (instance_resource, SqlStatement (query_string));
747+ ASSERT_TRUE (prepared_query.ok ()) << prepared_query.status ().message ();
748+
749+ auto bound_query = (*prepared_query).BindParameters ({});
750+ auto row_stream = client.ExecuteQuery (std::move (bound_query));
751+ std::vector<StatusOr<QueryRow>> rows;
752+ for (auto const & row : std::move (row_stream)) {
753+ rows.push_back (row);
754+ }
755+ ASSERT_EQ (rows.size (), 1 );
756+ ASSERT_TRUE (rows[0 ].ok ()) << rows[0 ].status ().message ();
757+ auto const & row = *rows[0 ];
758+ ASSERT_EQ (row.columns ().size (), 2 );
759+ EXPECT_EQ (row.columns ().at (0 ), " family4_history" );
760+
761+ auto value_hist = row.get (" family4_history" );
762+ ASSERT_TRUE (value_hist.ok ()) << value_hist.status ().message ();
763+ Value const & bigtable_val = *value_hist;
764+ using HistoryEntry = std::tuple<Timestamp, std::string>;
765+ auto history_map = bigtable_val.get <std::map<std::string, std::vector<HistoryEntry>>>();
766+ ASSERT_TRUE (history_map.ok ()) << history_array.status ().message ();
767+ ASSERT_EQ (history_map->size (), 2 );
768+
769+ // Verify cells returned ordered from newest to oldest.
770+ auto const & c1_entry0 = (*history_map)[" c1" ][0 ];
771+ auto ts_new = std::get<0 >(c1_entry0).get <sys_time<std::chrono::microseconds>>();
772+ ASSERT_STATUS_OK (ts_new);
773+ auto expected_current_time_ms =
774+ duration_cast<milliseconds>(std::chrono::microseconds (current_time));
775+ EXPECT_EQ (duration_cast<milliseconds>(ts_new->time_since_epoch ()),
776+ expected_current_time_ms);
777+ EXPECT_EQ (std::get<1 >(c1_entry0), column_1_value_new);
778+
779+ auto const & c1_entry1 = (*history_map)[" c1" ][1 ];
780+ auto ts_old = std::get<0 >(c1_entry1).get <sys_time<std::chrono::microseconds>>();
781+ ASSERT_STATUS_OK (ts_old);
782+ auto expected_old_time_ms =
783+ duration_cast<milliseconds>(std::chrono::microseconds (old_time));
784+ EXPECT_EQ (duration_cast<milliseconds>(ts_old->time_since_epoch ()),
785+ expected_old_time_ms);
786+ EXPECT_EQ (std::get<1 >(c1_entry1), column_1_value_old);
787+
788+ // Verify cells returned ordered from newest to oldest.
789+ auto const & c2_entry0 = (*history_map)[" c2" ][0 ];
790+ auto ts_new = std::get<0 >(c2_entry0).get <sys_time<std::chrono::microseconds>>();
791+ ASSERT_STATUS_OK (ts_new);
792+ auto expected_current_time_ms =
793+ duration_cast<milliseconds>(std::chrono::microseconds (current_time));
794+ EXPECT_EQ (duration_cast<milliseconds>(ts_new->time_since_epoch ()),
795+ expected_current_time_ms);
796+ EXPECT_EQ (std::get<1 >(c2_entry0), column_2_value_new);
797+
798+ auto const & c2_entry1 = (*history_map)[" c2" ][1 ];
799+ auto ts_old = std::get<0 >(c2_entry1).get <sys_time<std::chrono::microseconds>>();
800+ ASSERT_STATUS_OK (ts_old);
801+ auto expected_old_time_ms =
802+ duration_cast<milliseconds>(std::chrono::microseconds (old_time));
803+ EXPECT_EQ (duration_cast<milliseconds>(ts_old->time_since_epoch ()),
804+ expected_old_time_ms);
805+ EXPECT_EQ (std::get<1 >(c2_entry1), column_2_value_old);
806+ }
807+
675808// TODO(#8800) - remove after deprecation is complete
676809#include " google/cloud/internal/disable_deprecation_warnings.inc"
677810
0 commit comments