Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
build/
build-perf/
build-main/
build-perf-main/
cmake-out/
cmake-build-debug/
build-out/
Expand Down
31 changes: 31 additions & 0 deletions google/cloud/odbc/bq_driver/internal/data_translation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,37 @@ odbc_internal::StatusRecord ConvertFromStringDSValue(DSValue const& src_dsval,
src_len, required_chars,
dest_data.result_len);
}
if (dest_type == SQL_C_BINARY) {
if (!dest_buf) {
if (res_len) {
*res_len = static_cast<SQLLEN>(src_view.size());
}
return StatusRecord::Ok();
}
SQLLEN src_len = static_cast<SQLLEN>(src_view.size());
SQLLEN buf_len = dest_data.buflen;

if (buf_len <= 0) {
LOG(ERROR) << "ConvertFromStringDSValue::SQL_C_BINARY:: "
"Invalid buffer length: "
<< buf_len;
return StatusRecord{SQLStates::k_HY090(), "Invalid Buffer length"};
}

SQLLEN copy_len = (src_len < buf_len) ? src_len : buf_len;
if (copy_len > 0) {
std::memcpy(dest_buf, src_view.data(), static_cast<size_t>(copy_len));
}
if (res_len) {
*res_len = src_len;
}
if (copy_len < src_len) {
LOG(WARNING) << "ConvertFromStringDSValue::SQL_C_BINARY:: "
"String data, right truncated";
return StatusRecord{SQLStates::k_01004(), "String data, right truncated"};
}
return StatusRecord::Ok();
}

std::string src_str(src_view);

Expand Down
42 changes: 27 additions & 15 deletions google/cloud/odbc/bq_driver/internal/odbc_sql_execute_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -907,29 +907,45 @@ StatusRecordOr<DSResults> 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 =
Expand All @@ -942,10 +958,6 @@ StatusRecordOr<DSResults> 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;
}

Expand Down
64 changes: 40 additions & 24 deletions google/cloud/odbc/bq_driver/internal/odbc_sql_fetch.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ namespace google::cloud::odbc_bq_driver_internal {
using google::cloud::odbc_internal::SQLStates;
using google::cloud::odbc_internal::StatusRecord;

StatusRecord WriteToApplicationBuffer(DSValue const& ds_val,
StatusRecord WriteToApplicationBuffer(StatementHandle const& stmt_handle,
DSValue const& ds_val,
BQDataType bq_data_type,
DescriptorRecord& app_desc_rec,
SQLLEN bind_offset,
Expand Down Expand Up @@ -55,47 +56,59 @@ StatusRecord WriteToApplicationBuffer(DSValue const& ds_val,
}
// We need to reset the indicator_ptr once it has been set to SQL_NULL_DATA
// for DSNullValues.
if (indicator_ptr) {
*indicator_ptr = ds_val.size();
SQLLEN max_len = 0;
auto* conn = const_cast<StatementHandle&>(stmt_handle).GetConnectionHandle();

if (conn != nullptr) {
max_len = conn->GetDsn().default_string_column_length;
}

DSValue const* val_to_write = &ds_val;
DSValue truncated_val;
if (max_len > 0 && bq_data_type == BQDataType::kString &&
(target_c_type == SQL_C_CHAR || target_c_type == SQL_C_WCHAR) &&
ds_val.size() > static_cast<size_t>(max_len)) {
truncated_val.assign(ds_val.begin(), ds_val.begin() + max_len);
val_to_write = &truncated_val;
}

DataBuffer data = {target_c_type, app_buffer, app_buffer_len,
octet_length_ptr};
StatusRecord status_record;
switch (bq_data_type) {
case BQDataType::kInt64:
return ConvertFromArithmeticDSValue<SQLBIGINT>(ds_val, data);
return ConvertFromArithmeticDSValue<SQLBIGINT>(*val_to_write, data);
case BQDataType::kFloat64:
return ConvertFromArithmeticDSValue<SQLDOUBLE>(ds_val, data);
return ConvertFromArithmeticDSValue<SQLDOUBLE>(*val_to_write, data);
case BQDataType::kString:
return ConvertFromStringDSValue(ds_val, data);
return ConvertFromStringDSValue(*val_to_write, data);
case BQDataType::kDate:
return ConvertFromDateDSValue(ds_val, data);
return ConvertFromDateDSValue(*val_to_write, data);
case BQDataType::kTime:
return ConvertFromTimeDSValue(ds_val, data);
return ConvertFromTimeDSValue(*val_to_write, data);
case BQDataType::kJson:
return ConvertFromJsonDSValue(ds_val, data);
return ConvertFromJsonDSValue(*val_to_write, data);
case BQDataType::kStruct:
return ConvertFromStructDSValue(ds_val, data);
return ConvertFromStructDSValue(*val_to_write, data);
case BQDataType::kArray:
return ConvertFromArrayDSValue(ds_val, data);
return ConvertFromArrayDSValue(*val_to_write, data);
case BQDataType::kTimeStamp:
return ConvertFromTimestampDSValue(ds_val, data);
return ConvertFromTimestampDSValue(*val_to_write, data);
case BQDataType::kDatetime:
return ConvertFromDatetimeDSValue(ds_val, data);
return ConvertFromDatetimeDSValue(*val_to_write, data);
case BQDataType::kInterval:
return ConvertFromIntervalDSValue(ds_val, data);
return ConvertFromIntervalDSValue(*val_to_write, data);
case BQDataType::kBool:
return ConvertFromBooleanDSValue(ds_val, data);
return ConvertFromBooleanDSValue(*val_to_write, data);
case BQDataType::kGeography:
return ConvertFromGeographyDSValue(ds_val, data);
return ConvertFromGeographyDSValue(*val_to_write, data);
case BQDataType::kBytes:
return ConvertFromBytesDSValue(ds_val, data);
return ConvertFromBytesDSValue(*val_to_write, data);
case BQDataType::kRange:
return ConvertFromRangeDSValue(ds_val, data);
return ConvertFromRangeDSValue(*val_to_write, data);
case BQDataType::kBigNumeric:
case BQDataType::kNumeric:
return ConvertFromNumericDSValue(ds_val, data);
return ConvertFromNumericDSValue(*val_to_write, data);
}
LOG(ERROR) << "WriteToApplicationBuffer:: Data type not supported: "
<< bq_data_type;
Expand Down Expand Up @@ -147,8 +160,9 @@ SQLLEN GetElemSize(DescriptorRecord& app_desc_rec) {
}
}

StatusRecord WriteDSRow(DSRow const& ds_row, RowSchema const& schema,
DescriptorHandle& ard, int row_num) {
StatusRecord WriteDSRow(StatementHandle const& stmt_handle, DSRow const& ds_row,
RowSchema const& schema, DescriptorHandle& ard,
int row_num) {
SQLLEN* bind_offset_ptr = ard.GetHeaderRecord().bind_offset_ptr;
SQLLEN bind_offset = 0;
if (bind_offset_ptr) {
Expand Down Expand Up @@ -182,7 +196,7 @@ StatusRecord WriteDSRow(DSRow const& ds_row, RowSchema const& schema,
}

StatusRecord status_record = WriteToApplicationBuffer(
ds_val, bq_data_type, col_desc, bind_offset + row_offset,
stmt_handle, ds_val, bq_data_type, col_desc, bind_offset + row_offset,
bind_offset + row_offset_ind);
if (!status_record.ok()) {
LOG(ERROR) << "WriteDSRow::WriteToApplicationBuffer:: "
Expand All @@ -193,7 +207,8 @@ StatusRecord WriteDSRow(DSRow const& ds_row, RowSchema const& schema,
return StatusRecord::Ok();
}

StatusRecord WriteRowset(ResultSet const& result_set, int const rowset_size,
StatusRecord WriteRowset(StatementHandle const& stmt_handle,
ResultSet const& result_set, int const rowset_size,
DescriptorHandle& ard, DescriptorHandle& ird) {
if (rowset_size <= 0) {
LOG(ERROR) << "WriteRowset:: rowset_size should not be <= 0";
Expand All @@ -209,7 +224,8 @@ StatusRecord WriteRowset(ResultSet const& result_set, int const rowset_size,
for (int i = cursor; i < cursor + rowset_size && i < result_set.rows.size();
i++, row_counter++) {
StatusRecord status_record =
WriteDSRow(result_set.rows[i], result_set.row_schema, ard, i - cursor);
WriteDSRow(stmt_handle, result_set.rows[i], result_set.row_schema, ard,
i - cursor);
if (!status_record.ok()) {
LOG(ERROR) << "WriteRowset::WriteDSRow:: " << status_record.message;
return status_record;
Expand Down
6 changes: 4 additions & 2 deletions google/cloud/odbc/bq_driver/internal/odbc_sql_fetch.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@

namespace google::cloud::odbc_bq_driver_internal {

class StatementHandle;

// Writes rowset_size number of rows to the columns bound by the application
google::cloud::odbc_internal::StatusRecord WriteRowset(
ResultSet const& result_set, int rowset_size, DescriptorHandle& ard,
DescriptorHandle& ird);
StatementHandle const& stmt_handle, ResultSet const& result_set,
int rowset_size, DescriptorHandle& ard, DescriptorHandle& ird);

// Fetches the next batch of ResultSet rows
google::cloud::odbc_internal::StatusRecord FetchNextResultSet(
Expand Down
18 changes: 12 additions & 6 deletions google/cloud/odbc/bq_driver/internal/odbc_sql_fetch_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ TEST(WriteRowset, SuccessBasic) {
auto* double_populated = reinterpret_cast<SQLDOUBLE*>(double_buf);
for (auto const& k_testing_result_set_value : kTestingResultSetValues) {
result_set.cursor++;
StatusRecord status_record = WriteRowset(result_set, 1, ard, ird);
StatusRecord status_record =
WriteRowset(stmt_handle, result_set, 1, ard, ird);
EXPECT_TRUE(status_record.ok());
EXPECT_EQ(rows_processed, 1);
SQLBIGINT int_expected = k_testing_result_set_value.int_field;
Expand Down Expand Up @@ -164,7 +165,8 @@ TEST(WriteRowset, SuccessMultiplerows) {
int num_rows_to_write =
std::min(static_cast<int>(kTestingResultSetValues.size() - i), kRsSize);
result_set.cursor++;
StatusRecord status_record = WriteRowset(result_set, kRsSize, ard, ird);
StatusRecord status_record =
WriteRowset(stmt_handle, result_set, kRsSize, ard, ird);
EXPECT_TRUE(status_record.ok());
// Verify if the field corresponding to stmt attribute
// SQL_ATTR_ROWS_FETCHED_PTR was populated
Expand Down Expand Up @@ -207,7 +209,8 @@ TEST(WriteRowset, SuccessWithoffset) {
auto* int_populated = reinterpret_cast<SQLBIGINT*>(int_buf + bound_offset);
for (auto const& k_testing_result_set_value : kTestingResultSetValues) {
result_set.cursor++;
StatusRecord status_record = WriteRowset(result_set, 1, ard, ird);
StatusRecord status_record =
WriteRowset(stmt_handle, result_set, 1, ard, ird);
EXPECT_TRUE(status_record.ok());
SQLBIGINT int_expected = k_testing_result_set_value.int_field;
if (int_expected == kNullInt) {
Expand Down Expand Up @@ -240,7 +243,8 @@ TEST(WriteRowset, SuccessFailNullindicator) {

auto* int_populated = reinterpret_cast<SQLBIGINT*>(int_buf);
result_set.cursor++;
StatusRecord status_record = WriteRowset(result_set, 1, ard, ird);
StatusRecord status_record =
WriteRowset(stmt_handle, result_set, 1, ard, ird);
EXPECT_FALSE(status_record.ok());
EXPECT_EQ(SQLStates::k_22002(), status_record.sql_state);
EXPECT_EQ("Indicator variable required but not supplied",
Expand All @@ -262,7 +266,8 @@ TEST(WriteRowset, FailureTranslationoutofrange) {
DescriptorHandle& ard = stmt_handle.GetDescriptorHandle(DescriptorType::kARD);
DescriptorHandle& ird = stmt_handle.GetDescriptorHandle(DescriptorType::kIRD);
result_set.cursor++;
StatusRecord status_record = WriteRowset(result_set, 1, ard, ird);
StatusRecord status_record =
WriteRowset(stmt_handle, result_set, 1, ard, ird);
EXPECT_FALSE(status_record.ok());
EXPECT_EQ(SQLStates::k_22003(), status_record.sql_state);
EXPECT_EQ("Numeric value out of range", status_record.message);
Expand All @@ -286,7 +291,8 @@ TEST(WriteRowset, FailureFractionaltruncation) {
DescriptorHandle& ard = stmt_handle.GetDescriptorHandle(DescriptorType::kARD);
DescriptorHandle& ird = stmt_handle.GetDescriptorHandle(DescriptorType::kIRD);
result_set.cursor++;
StatusRecord status_record = WriteRowset(result_set, 1, ard, ird);
StatusRecord status_record =
WriteRowset(stmt_handle, result_set, 1, ard, ird);
EXPECT_FALSE(status_record.ok());
EXPECT_EQ(SQLStates::k_01S07(), status_record.sql_state);
EXPECT_EQ("Fractional truncation", status_record.message);
Expand Down
22 changes: 12 additions & 10 deletions google/cloud/odbc/bq_driver/internal/odbc_stmt_handle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -412,14 +412,15 @@ StatusRecord StatementHandle::PopulateIrd(DescriptorHandle& descriptor_handle,
descriptor_record.SetNumPrecRadix(kDefaultIntervalPrecision);
}

if (res.type == "TIME" || res.type == "DATETIME") {
if (res.type == "TIME" || res.type == "DATETIME" ||
res.type == "TIMESTAMP") {
descriptor_record.precision = 6;
descriptor_record.scale = 6;
} else if (res.type == "TIMESTAMP" || res.type == "DATE") {
descriptor_record.precision;
descriptor_record.scale = type_info.maximum_scale;
} else if (res.type == "DATE") {
descriptor_record.precision = 0;
descriptor_record.scale = 0;
} else {
descriptor_record.precision = type_info.interval_precision == NULL
descriptor_record.precision = type_info.interval_precision == 0
? type_info.col_size
: type_info.interval_precision;
descriptor_record.scale = type_info.maximum_scale;
Expand Down Expand Up @@ -520,14 +521,15 @@ StatusRecord StatementHandle::PopulateIpd(DescriptorHandle& handle,
type_info);

if (stmt_params[i].parameter_type.type == "TIME" ||
stmt_params[i].parameter_type.type == "DATETIME") {
stmt_params[i].parameter_type.type == "DATETIME" ||
stmt_params[i].parameter_type.type == "TIMESTAMP") {
descriptor_record.precision = 6;
descriptor_record.scale = 6;
} else if (stmt_params[i].parameter_type.type == "TIMESTAMP" ||
stmt_params[i].parameter_type.type == "DATE") {
descriptor_record.precision;
} else if (stmt_params[i].parameter_type.type == "DATE") {
descriptor_record.precision = 0;
descriptor_record.scale = 0;
} else {
descriptor_record.precision = type_info.interval_precision == NULL
descriptor_record.precision = type_info.interval_precision == 0
? type_info.col_size
: type_info.interval_precision;
descriptor_record.scale = (stmt_params[i].parameter_type.type == "RANGE")
Expand Down
Loading
Loading