From 7283cf33d2ef5eb3030136a4e67d732e5ef6fed9 Mon Sep 17 00:00:00 2001 From: Sachin Purohit Date: Wed, 20 May 2026 16:36:22 +0000 Subject: [PATCH 1/2] chore: using jobs.query rather than READ API for single page --- .../internal/odbc_sql_execute_utils.cc | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc b/google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc index 9f9ba23355..03032f5d28 100644 --- a/google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc +++ b/google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc @@ -982,29 +982,45 @@ StatusRecordOr FetchBQData( StatementHandle& stmt_handle, PostQueryRequest const& post_query_request, [[maybe_unused]] bool with_htapi) { ConnectionHandle& conn_handle = *(stmt_handle.GetConnectionHandle()); + + auto pq_status = PostQueryWithoutResults(conn_handle, post_query_request); + if (!pq_status) { + return pq_status.GetStatusRecord(); + } + + // If session started, propagate session ID to the connection handle + if (!conn_handle.IsSessionStarted() && + !pq_status->session_info.session_id.empty()) { + conn_handle.SetSessionId(pq_status->session_info.session_id); + } + + DSResults results; + results.num_dml_affected_rows = pq_status->num_dml_affected_rows; + results.job_ref = pq_status->job_reference; + stmt_handle.GetPagingInfo().job_id = pq_status->job_reference.job_id; + stmt_handle.GetPagingInfo().page_token = pq_status->page_token; + + if (pq_status->job_complete && pq_status->page_token.empty()) { + // Only one page of results, return it directly. + results.data_source_results = *pq_status; + return results; + } + + // If there are more pages, check if we should use HTAPI fallback #if (!defined(_WIN32) || defined(_WIN64)) && !defined(NO_ARROW) if (with_htapi && conn_handle.GetDsn().allow_htapi) { + // Fallback to HTAPI StatusRecord read_status = FetchBQDataRead(stmt_handle, post_query_request); if (!read_status.ok()) { return read_status; } - DSResults results; results.data_source_results = stmt_handle.GetResultSet(); return results; } #endif // (!defined(_WIN32) || defined(_WIN64)) && !defined(NO_ARROW) - auto pq_status = PostQueryWithoutResults(conn_handle, post_query_request); - if (!pq_status) { - return pq_status.GetStatusRecord(); - } - DSResults results; - results.num_dml_affected_rows = pq_status->num_dml_affected_rows; - results.job_ref = pq_status->job_reference; - stmt_handle.GetPagingInfo().job_id = pq_status->job_reference.job_id; - stmt_handle.GetPagingInfo().page_token = pq_status->page_token; + // Otherwise, continue with standard REST API pagination if (pq_status->job_complete) { - // we have gotten all the results results.data_source_results = *pq_status; } else { auto gq_status = @@ -1017,10 +1033,6 @@ StatusRecordOr FetchBQData( results.num_dml_affected_rows = gq_status->num_dml_affected_rows; results.data_source_results = *gq_status; } - if (!conn_handle.IsSessionStarted() && - !pq_status->session_info.session_id.empty()) { - conn_handle.SetSessionId(pq_status->session_info.session_id); - } return results; } From 48ed9119a23d82b96eb3a00190c77c281b0646fc Mon Sep 17 00:00:00 2001 From: Sachin Purohit Date: Wed, 26 Aug 2026 13:56:23 +0000 Subject: [PATCH 2/2] fix: resolved timestamp formatting issue in REST flow --- .../odbc/bq_driver/internal/odbc_internal_commons.h | 4 ++-- .../bq_driver/internal/odbc_internal_commons_test.cc | 4 ++-- .../odbc/bq_driver/internal/odbc_sql_execute_utils.cc | 11 +++++++++-- .../odbc_driver_tests/data_translation_test.cc | 6 ------ .../odbc_driver_tests/statement_test.cc | 6 ++---- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/google/cloud/odbc/bq_driver/internal/odbc_internal_commons.h b/google/cloud/odbc/bq_driver/internal/odbc_internal_commons.h index cf42c98e17..6cc3e4a6e6 100644 --- a/google/cloud/odbc/bq_driver/internal/odbc_internal_commons.h +++ b/google/cloud/odbc/bq_driver/internal/odbc_internal_commons.h @@ -309,8 +309,8 @@ inline std::string FormatDatetimeToString( const SQL_TIMESTAMP_STRUCT& datetime) { char buffer[30]; auto const* datetime_format = (datetime.fraction == 0) - ? "%04d-%02d-%02dT%02d:%02d:%02d" - : "%04d-%02d-%02dT%02d:%02d:%02d.%06d"; + ? "%04d-%02d-%02d %02d:%02d:%02d" + : "%04d-%02d-%02d %02d:%02d:%02d.%06d"; snprintf(buffer, sizeof(buffer), datetime_format, datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second, datetime.fraction); diff --git a/google/cloud/odbc/bq_driver/internal/odbc_internal_commons_test.cc b/google/cloud/odbc/bq_driver/internal/odbc_internal_commons_test.cc index 886ee06265..c03481684b 100644 --- a/google/cloud/odbc/bq_driver/internal/odbc_internal_commons_test.cc +++ b/google/cloud/odbc/bq_driver/internal/odbc_internal_commons_test.cc @@ -269,7 +269,7 @@ TEST(FormatDatetimeToString, DatetimeStringWithZeros) { std::string datetime_string = FormatDatetimeToString(datetime); - std::string expected_string = "2020-01-10T00:05:03"; + std::string expected_string = "2020-01-10 00:05:03"; EXPECT_EQ(datetime_string, expected_string); } @@ -285,7 +285,7 @@ TEST(FormatDatetimeToString, DatetimeString) { std::string datetime_string = FormatDatetimeToString(datetime); - std::string expected_string = "2020-01-10T01:59:43.123456"; + std::string expected_string = "2020-01-10 01:59:43.123456"; EXPECT_EQ(datetime_string, expected_string); } diff --git a/google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc b/google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc index 03032f5d28..7994937e2c 100644 --- a/google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc +++ b/google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc @@ -306,9 +306,16 @@ StatusRecordOr> GetArrowSchema( case arrow::Type::BOOL: col_schema.col_type = BQDataType::kBool; break; - case arrow::Type::TIMESTAMP: - col_schema.col_type = BQDataType::kTimeStamp; + case arrow::Type::TIMESTAMP: { + auto ts_type = + std::static_pointer_cast(field->type()); + if (ts_type->timezone().empty()) { + col_schema.col_type = BQDataType::kDatetime; + } else { + col_schema.col_type = BQDataType::kTimeStamp; + } break; + } case arrow::Type::TIME64: col_schema.col_type = BQDataType::kTime; break; diff --git a/google/cloud/odbc/integration_tests/odbc_driver_tests/data_translation_test.cc b/google/cloud/odbc/integration_tests/odbc_driver_tests/data_translation_test.cc index 82631a9e55..1973d34b6c 100644 --- a/google/cloud/odbc/integration_tests/odbc_driver_tests/data_translation_test.cc +++ b/google/cloud/odbc/integration_tests/odbc_driver_tests/data_translation_test.cc @@ -2486,9 +2486,6 @@ void TestTranslationsFromDateTime(std::shared_ptr conn, switch (expected.target_c_type) { case SQL_C_CHAR: { std::string returned_val = reinterpret_cast(data); - if (kIsBqDriver) { - expected_val = FormatToGoogleDatetimeStr(expected_val); - } EXPECT_EQ(returned_val, expected_val); break; } @@ -2496,9 +2493,6 @@ void TestTranslationsFromDateTime(std::shared_ptr conn, SQLINTEGER length = strlen_or_ind / sizeof(SQLWCHAR); std::string returned_val = ConvertSQLWCHARToString(reinterpret_cast(data), length); - if (kIsBqDriver) { - expected_val = FormatToGoogleDatetimeStr(expected_val); - } EXPECT_STREQ(returned_val.data(), expected_val.data()); break; } diff --git a/google/cloud/odbc/integration_tests/odbc_driver_tests/statement_test.cc b/google/cloud/odbc/integration_tests/odbc_driver_tests/statement_test.cc index f4f93443dd..ef3d052b72 100644 --- a/google/cloud/odbc/integration_tests/odbc_driver_tests/statement_test.cc +++ b/google/cloud/odbc/integration_tests/odbc_driver_tests/statement_test.cc @@ -528,12 +528,10 @@ static RowWiseResults const kBasicTypesExpected{ {6, "{\"age\":30,\"name\":\"John\"}"}, {7, "2025-11-12 23:22:27.500000"}, {8, kIsBqDriver ? "12:34:56" : "12:34:56.000000"}, - {9, kIsBqDriver - ? (kIsWin32 ? "2024-05-01T08:00:00" : "2024-05-01 08:00:00") - : "2024-05-01 08:00:00.000000"}, + {9, kIsBqDriver ? "2024-05-01 08:00:00" : "2024-05-01 08:00:00.000000"}, {10, "2023-04-01"}, {11, kIsBqDriver - ? (kIsWin32 ? "[\"3\",\"4\",\"5\"]" : "[3, 4, 5]") + ? "[\"3\",\"4\",\"5\"]" : "{\"v\":[{\"v\":\"3\"},{\"v\":\"4\"},{\"v\":\"5\"}]}"}, }}, };