Skip to content

Commit 4884683

Browse files
committed
feat: reading from the job-level stream
1 parent eb5fe71 commit 4884683

5 files changed

Lines changed: 152 additions & 109 deletions

File tree

google/cloud/odbc/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ if (NOT google_cloud_cpp_bigquery_rest_FOUND OR NOT
147147

148148
FetchContent_Declare(
149149
google-cloud-cpp
150-
URL https://github.com/googleapis/google-cloud-cpp/archive/85bfaff9038e0791c7f48a995253dc20c307ac78.tar.gz
150+
URL https://github.com/sachinpro/google-cloud-cpp/archive/828745838c6420f4ed5d815bb4bcd013fe1fd93c.tar.gz
151151
PATCH_COMMAND ${CMAKE_COMMAND} -P
152152
${CMAKE_SOURCE_DIR}/cmake/PatchGoogleCloudCpp.cmake)
153153
FetchContent_MakeAvailable(google-cloud-cpp)

google/cloud/odbc/bq_driver/internal/odbc_internal_commons.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,9 @@ PostQueryRequest ConstructBasicPostQueryRequest(
10621062
if (is_job_creation_required) {
10631063
query_request.set_job_creation_mode(JobCreationMode::Required());
10641064
}
1065+
if (conn_handle.GetDsn().allow_htapi) {
1066+
query_request.set_query_results_format("ARROW");
1067+
}
10651068
if (!default_dataset.empty()) {
10661069
DatasetReference ds_ref;
10671070
// Set dataset info.

google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc

Lines changed: 146 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,10 @@ StatusRecordOr<DSResults> ExecuteScript(
271271

272272
#if (!defined(_WIN32) || defined(_WIN64)) && !defined(NO_ARROW)
273273

274-
StatusRecordOr<std::shared_ptr<arrow::Schema>> GetArrowSchema(
275-
::google::cloud::bigquery::storage::v1::ArrowSchema const& schema_in,
276-
RowSchema& row_schema) {
274+
StatusRecordOr<std::shared_ptr<arrow::Schema>> GetArrowSchemaFromBytes(
275+
std::string const& serialized_schema, RowSchema& row_schema) {
277276
std::shared_ptr<arrow::Buffer> buffer =
278-
std::make_shared<arrow::Buffer>(schema_in.serialized_schema());
277+
std::make_shared<arrow::Buffer>(serialized_schema);
279278
arrow::io::BufferReader buffer_reader(buffer);
280279
arrow::ipc::DictionaryMemo dictionary_memo;
281280
auto result = arrow::ipc::ReadSchema(&buffer_reader, &dictionary_memo);
@@ -349,12 +348,17 @@ StatusRecordOr<std::shared_ptr<arrow::Schema>> GetArrowSchema(
349348
return schema;
350349
}
351350

352-
StatusRecordOr<std::shared_ptr<arrow::RecordBatch>> GetArrowRecordBatch(
353-
::google::cloud::bigquery::storage::v1::ArrowRecordBatch const&
354-
record_batch_in,
355-
std::shared_ptr<arrow::Schema> schema) {
356-
std::shared_ptr<arrow::Buffer> buffer = std::make_shared<arrow::Buffer>(
357-
record_batch_in.serialized_record_batch());
351+
StatusRecordOr<std::shared_ptr<arrow::Schema>> GetArrowSchema(
352+
::google::cloud::bigquery::storage::v1::ArrowSchema const& schema_in,
353+
RowSchema& row_schema) {
354+
return GetArrowSchemaFromBytes(schema_in.serialized_schema(), row_schema);
355+
}
356+
357+
StatusRecordOr<std::shared_ptr<arrow::RecordBatch>>
358+
GetArrowRecordBatchFromBytes(std::string const& serialized_record_batch,
359+
std::shared_ptr<arrow::Schema> schema) {
360+
std::shared_ptr<arrow::Buffer> buffer =
361+
std::make_shared<arrow::Buffer>(serialized_record_batch);
358362
arrow::io::BufferReader buffer_reader(buffer);
359363
arrow::ipc::DictionaryMemo dictionary_memo;
360364
arrow::ipc::IpcReadOptions read_options;
@@ -368,6 +372,14 @@ StatusRecordOr<std::shared_ptr<arrow::RecordBatch>> GetArrowRecordBatch(
368372
return record_batch;
369373
}
370374

375+
StatusRecordOr<std::shared_ptr<arrow::RecordBatch>> GetArrowRecordBatch(
376+
::google::cloud::bigquery::storage::v1::ArrowRecordBatch const&
377+
record_batch_in,
378+
std::shared_ptr<arrow::Schema> schema) {
379+
return GetArrowRecordBatchFromBytes(
380+
record_batch_in.serialized_record_batch(), std::move(schema));
381+
}
382+
371383
StatusRecord ProcessRecordBatch(
372384
std::shared_ptr<arrow::Schema> schema,
373385
std::shared_ptr<arrow::RecordBatch> record_batch, ResultSet& result_set) {
@@ -724,11 +736,26 @@ StatusRecord ReadNextResultsFromStream(StatementHandle& stmt_handle) {
724736
return StatusRecord::ConvertFrom(read_row_status.status());
725737
}
726738
ReadRowsResponse row = *read_row_status;
727-
if (row.has_arrow_record_batch()) {
739+
if (row.has_arrow_schema() && !stmt_handle.GetArrowSchema()) {
740+
ResultSet& result_set = stmt_handle.GetResultSet();
741+
StatusRecordOr<std::shared_ptr<arrow::Schema>> schema_status =
742+
GetArrowSchema(row.arrow_schema(), result_set.row_schema);
743+
if (!schema_status) {
744+
return schema_status.GetStatusRecord();
745+
}
746+
stmt_handle.SetArrowSchema(*schema_status);
747+
}
748+
if (row.has_arrow_record_batch() &&
749+
!row.arrow_record_batch().serialized_record_batch().empty()) {
728750
// The schema is coming from ResultSet cached in the statement handle.
729751
// We don't want to generate the schema again for every batch since it
730752
// will remain the same.
731753
std::shared_ptr<arrow::Schema> schema = stmt_handle.GetArrowSchema();
754+
if (!schema) {
755+
return StatusRecord{
756+
SQLStates::k_HY000(),
757+
"Internal Error: Arrow schema missing for record batch"};
758+
}
732759
StatusRecordOr<std::shared_ptr<arrow::RecordBatch>> record_batch_status =
733760
GetArrowRecordBatch(row.arrow_record_batch(), schema);
734761
if (!record_batch_status) {
@@ -741,22 +768,47 @@ StatusRecord ReadNextResultsFromStream(StatementHandle& stmt_handle) {
741768
// cursor to default.
742769
result_set.cursor = -1;
743770
return ProcessRecordBatch(schema, *record_batch_status, result_set);
744-
} else {
745-
return StatusRecord{
746-
SQLStates::k_HY000(),
747-
"Internal Error: cannot find arrow record batch to process!"};
748771
}
749-
} else {
750-
stmt_handle.ClearReadRowsStream();
751-
stmt_handle.ClearReadRowsIterator();
752-
// Empty result set.
753-
stmt_handle.GetResultSet().rows.clear();
754-
stmt_handle.GetResultSet().cursor = -1;
755-
LOG(INFO) << "FetchBQDataReadArrow:: Read stream ended.";
772+
++(*optional_it);
773+
stmt_handle.SetReadRowsIterator(*optional_it);
756774
}
775+
776+
stmt_handle.ClearReadRowsStream();
777+
stmt_handle.ClearReadRowsIterator();
778+
// Empty result set.
779+
stmt_handle.GetResultSet().rows.clear();
780+
stmt_handle.GetResultSet().cursor = -1;
781+
LOG(INFO) << "FetchBQDataReadArrow:: Read stream ended.";
757782
return StatusRecord::Ok();
758783
}
759784

785+
StatusRecord FetchBQDataReadJobArrow(StatementHandle& stmt_handle,
786+
std::string const& project_id,
787+
std::string const& location,
788+
std::string const& job_id,
789+
int64_t offset = 0) {
790+
std::string read_stream_name = "projects/" + project_id + "/locations/" +
791+
location + "/jobs/" + job_id +
792+
"/streams/_default";
793+
794+
ConnectionHandle& conn_handle = *(stmt_handle.GetConnectionHandle());
795+
Options options;
796+
options.set<MaxRetriesOption>(conn_handle.GetDsn().max_retries);
797+
auto bq_client = conn_handle.GetClient();
798+
799+
ReadRowsRequest read_rows_request;
800+
read_rows_request.set_read_stream(read_stream_name);
801+
if (offset > 0) {
802+
read_rows_request.set_offset(offset);
803+
}
804+
805+
StreamRange<google::cloud::bigquery::storage::v1::ReadRowsResponse>
806+
read_rows_stream =
807+
bq_client->GetReadRowsStream(read_rows_request, options);
808+
stmt_handle.SetReadRowsStream(std::move(read_rows_stream));
809+
return ReadNextResultsFromStream(stmt_handle);
810+
}
811+
760812
StatusRecord FetchBQDataReadArrow(StatementHandle& stmt_handle,
761813
TableReference& table_ref) {
762814
std::string project_id = table_ref.project_id;
@@ -874,105 +926,91 @@ StatusRecord CreateLargeDatasetIfNeeded(std::shared_ptr<ODBCBQClient> bq_client,
874926

875927
StatusRecord FetchBQDataRead(StatementHandle& stmt_handle,
876928
PostQueryRequest const& post_query_request) {
877-
QueryRequest query_request = post_query_request.query_request();
878-
std::string query = query_request.query();
879-
Job job;
880-
job.configuration.query.query = query;
881-
job.configuration.query.use_query_cache = true;
882-
job.configuration.dry_run = false;
883-
job.configuration.query.allow_large_results = true;
884-
job.configuration.query.use_legacy_sql = false;
885-
job.configuration.query.create_disposition = "CREATE_IF_NEEDED";
886-
job.configuration.query.write_disposition = "WRITE_TRUNCATE";
887-
job.configuration.query.query_parameters = query_request.query_parameters();
888-
889929
ConnectionHandle& conn_handle = *(stmt_handle.GetConnectionHandle());
890930
auto dsn = conn_handle.GetDsn();
891-
std::string catalog_name = dsn.catalog;
892-
std::string default_dataset = dsn.default_dataset;
893-
if (!default_dataset.empty()) {
894-
job.configuration.query.default_dataset.project_id = catalog_name;
895-
job.configuration.query.default_dataset.dataset_id = default_dataset;
896-
}
897-
job.configuration.query.destination_table.project_id = catalog_name;
898-
job.configuration.query.destination_table.dataset_id =
899-
dsn.use_default_large_results_dataset ? kDefaultDestDatasetId
900-
: dsn.large_results_dataset_id;
901-
std::string table_id = GenerateTableId();
902-
job.configuration.query.destination_table.table_id = table_id;
903-
904-
job.job_reference.job_id = "job_" + table_id;
905-
job.job_reference.project_id = catalog_name;
906-
907-
job.configuration.query.parameter_mode = "POSITIONAL";
908-
job.configuration.query.allow_large_results = true;
909-
910931
Options opt;
911932
opt.set<MaxRetriesOption>(dsn.max_retries);
912933
auto bq_client = conn_handle.GetClient();
913-
std::string dataset_location;
914-
// Check if the destination dataset (LargeResultsDataSetId) exists.
915-
// 1. If it exists, we execute the query job in the same region as the
916-
// destination dataset.
917-
// 2. If it does not exist (404), we create it in the region where the query
918-
// would run
919-
// (determined by the dry-run, which resolves to the DefaultDataset region,
920-
// source tables region, or defaults to US). Subsequent runs will find the
921-
// dataset and execute in its region.
922-
auto response = bq_client->GetDataset(
923-
catalog_name, job.configuration.query.destination_table.dataset_id, opt);
924-
if (!response.Ok()) {
925-
StatusRecord err_status = response.GetStatusRecord();
926-
if (err_status.native_error_code == 404) {
927-
dataset_location = query_request.location();
928-
// We need to first create large results dataset if it was not there
929-
StatusRecord create_dataset_status = CreateLargeDatasetIfNeeded(
930-
bq_client, dsn.catalog,
931-
job.configuration.query.destination_table.dataset_id,
932-
dataset_location, dsn.large_table_expiration_time, opt);
933-
if (!create_dataset_status.ok()) {
934-
return create_dataset_status;
935-
}
936-
} else {
937-
return err_status;
938-
}
939-
} else {
940-
dataset_location = response->location;
934+
if (!bq_client) {
935+
LOG(ERROR) << "FetchBQDataRead:: Invalid or null BQ Client within the "
936+
"connection handle.";
937+
return StatusRecord{
938+
SQLStates::k_HY000(),
939+
"Invalid or null BQ Client within the connection handle"};
941940
}
942941

943-
job.job_reference.location = dataset_location;
942+
auto pq_status = PostQueryWithoutResults(conn_handle, post_query_request);
943+
if (!pq_status.Ok()) {
944+
return pq_status.GetStatusRecord();
945+
}
944946

945-
// Insert job
946-
auto insert_response = bq_client->InsertJob(dsn.catalog, job, opt);
947-
if (!insert_response.Ok()) {
948-
return insert_response.GetStatusRecord();
947+
std::string project_id = pq_status->job_reference.project_id;
948+
std::string job_id = pq_status->job_reference.job_id;
949+
std::string location = pq_status->job_reference.location;
950+
if (location.empty()) {
951+
location = post_query_request.query_request().location();
949952
}
950-
// Here we are replacing the dry run Job created during SQLPrepare.
951-
// This should be safe since the same query is executed during HTAPI flow too.
952-
stmt_handle.SetPreparedJob(*insert_response);
953-
954-
// Wait for Job to complete
955-
std::string job_status = insert_response->status.state;
956-
ExponentialBackoffPolicy backoff(chrono_ms(100), chrono_ms(200), 2);
957-
StatusRecordOr<Job> get_job_response = insert_response;
958-
while (job_status != "DONE") {
959-
std::this_thread::sleep_for(backoff.OnCompletion());
960-
get_job_response = bq_client->GetJob(
961-
conn_handle.GetDsn().catalog, insert_response->job_reference.job_id,
962-
insert_response->job_reference.location, opt);
963-
if (!get_job_response.Ok()) {
964-
return get_job_response.GetStatusRecord();
953+
if (location.empty()) {
954+
location = "US";
955+
}
956+
957+
stmt_handle.GetPagingInfo().job_id = job_id;
958+
959+
if (!pq_status->job_complete) {
960+
ExponentialBackoffPolicy backoff(chrono_ms(100), chrono_ms(200), 2);
961+
StatusRecordOr<Job> get_job_response;
962+
std::string job_state = "RUNNING";
963+
while (job_state != "DONE") {
964+
std::this_thread::sleep_for(backoff.OnCompletion());
965+
get_job_response = bq_client->GetJob(project_id, job_id, location, opt);
966+
if (!get_job_response.Ok()) {
967+
return get_job_response.GetStatusRecord();
968+
}
969+
job_state = get_job_response->status.state;
970+
}
971+
if (!get_job_response->status.error_result.message.empty()) {
972+
LOG(ERROR) << "FetchBQDataRead:: "
973+
<< get_job_response->status.error_result.message;
974+
return StatusRecord{SQLStates::k_HY000(),
975+
get_job_response->status.error_result.message};
976+
}
977+
if (location.empty() && !get_job_response->job_reference.location.empty()) {
978+
location = get_job_response->job_reference.location;
965979
}
966-
job_status = get_job_response->status.state;
967980
}
968-
std::string error_message = get_job_response->status.error_result.message;
969-
if (!error_message.empty()) {
970-
LOG(ERROR) << "FetchBQDataRead:: " << error_message;
971-
return StatusRecord{SQLStates::k_HY000(), error_message};
981+
982+
int64_t fetched_rows = 0;
983+
if (!pq_status->arrow_schema.serialized_schema.empty() &&
984+
!pq_status->arrow_record_batch.serialized_record_batch.empty()) {
985+
auto& result_set = stmt_handle.GetResultSet();
986+
auto schema_status = GetArrowSchemaFromBytes(
987+
pq_status->arrow_schema.serialized_schema, result_set.row_schema);
988+
if (schema_status.Ok()) {
989+
auto schema = *schema_status;
990+
stmt_handle.SetArrowSchema(schema);
991+
auto batch_status = GetArrowRecordBatchFromBytes(
992+
pq_status->arrow_record_batch.serialized_record_batch, schema);
993+
if (batch_status.Ok()) {
994+
auto batch = *batch_status;
995+
fetched_rows = batch->num_rows();
996+
result_set.cursor = -1;
997+
auto process_status = ProcessRecordBatch(schema, batch, result_set);
998+
if (!process_status.ok()) {
999+
return process_status;
1000+
}
1001+
if (pq_status->job_complete && pq_status->page_token.empty() &&
1002+
static_cast<uint64_t>(result_set.rows.size()) >=
1003+
pq_status->total_rows) {
1004+
stmt_handle.ClearReadRowsStream();
1005+
stmt_handle.ClearReadRowsIterator();
1006+
return StatusRecord::Ok();
1007+
}
1008+
}
1009+
}
9721010
}
9731011

974-
return FetchBQDataReadArrow(
975-
stmt_handle, insert_response->configuration.query.destination_table);
1012+
return FetchBQDataReadJobArrow(stmt_handle, project_id, location, job_id,
1013+
/*offset=*/fetched_rows);
9761014
}
9771015

9781016
#endif // (!defined(_WIN32) || defined(_WIN64)) && !defined(NO_ARROW)

google/cloud/odbc/bq_driver/internal/utils.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "google/cloud/odbc/bq_driver/internal/trace_utils.h"
2121
#include "google/cloud/odbc/bq_driver/internal/utils.h"
2222
#include "google/cloud/internal/getenv.h"
23+
#include "absl/types/optional.h"
2324
#include <array>
2425
#include <atomic>
2526
#include <cstdint>

google/cloud/odbc/testing/utils/env_vars.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
#include "google/cloud/internal/getenv.h"
16+
#include "absl/types/optional.h"
1617
#include <stdexcept>
1718

1819
namespace google::cloud::odbc_testing_utils {

0 commit comments

Comments
 (0)