Skip to content

Commit c5cd918

Browse files
impl(bq_driver): Picosecond support
1 parent 476562f commit c5cd918

12 files changed

Lines changed: 545 additions & 183 deletions

File tree

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

Lines changed: 157 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -935,26 +935,110 @@ odbc_internal::StatusRecord ConvertFromTimeDSValue(DSValue const& src_dsval,
935935
return status_record;
936936
}
937937

938+
odbc_internal::StatusRecord ConvertTimestampStringToChar(
939+
const std::string& timestamp_src_str,
940+
void* dest_buf,
941+
SQLLEN buffer_length,
942+
SQLLEN* res_len) {
943+
944+
SQLLEN timestamp_src_len =
945+
static_cast<SQLLEN>(timestamp_src_str.size());
946+
auto* dest = reinterpret_cast<char*>(dest_buf);
947+
StatusRecord status_record;
948+
949+
if (buffer_length > timestamp_src_len) {
950+
if (res_len) *res_len = timestamp_src_len;
951+
std::strncpy(dest, timestamp_src_str.c_str(), timestamp_src_len);
952+
dest[timestamp_src_len] = '\0';
953+
} else if (20 <= buffer_length && buffer_length <= timestamp_src_len) {
954+
if (res_len) *res_len = buffer_length;
955+
std::strncpy(dest, timestamp_src_str.c_str(), buffer_length - 1);
956+
dest[buffer_length - 1] = '\0';
957+
status_record = StatusRecord{SQLStates::k_01004(), "Data truncated"};
958+
} else {
959+
status_record =
960+
StatusRecord{SQLStates::k_22003(), "Buffer length is insufficient"};
961+
}
962+
return status_record;
963+
}
964+
965+
odbc_internal::StatusRecord ConvertTimestampStringToWChar(
966+
const std::string& timestamp_src_str,
967+
void* dest_buf,
968+
SQLLEN buffer_length,
969+
SQLLEN* res_len) {
970+
971+
auto wstr_or = Utf8ToUtf16(timestamp_src_str);
972+
if (!wstr_or) {
973+
return StatusRecord{
974+
SQLStates::k_HY000(),
975+
"DSValueToWchar Conversion Failed"};
976+
}
977+
978+
std::vector<SQLWCHAR> wstr_data(
979+
wstr_or->begin(), wstr_or->end());
980+
wstr_data.emplace_back(L'\0');
981+
982+
auto* dest = reinterpret_cast<SQLWCHAR*>(dest_buf);
983+
984+
SQLLEN timestamp_src_len =
985+
static_cast<SQLLEN>(wstr_or->size());
986+
987+
SQLLEN wchar_capacity =
988+
buffer_length / sizeof(SQLWCHAR);
989+
990+
StatusRecord status_record = StatusRecord::Ok();
991+
992+
if (wchar_capacity > timestamp_src_len) {
993+
if (res_len) {
994+
*res_len = timestamp_src_len * sizeof(SQLWCHAR);
995+
}
996+
997+
std::memcpy(dest, wstr_data.data(),
998+
timestamp_src_len * sizeof(SQLWCHAR));
999+
1000+
dest[timestamp_src_len] = L'\0';
1001+
1002+
} else if (20 <= wchar_capacity &&
1003+
wchar_capacity <= timestamp_src_len) {
1004+
1005+
if (res_len) {
1006+
*res_len = wchar_capacity * sizeof(SQLWCHAR);
1007+
}
1008+
1009+
std::memcpy(dest, wstr_data.data(),
1010+
wchar_capacity * sizeof(SQLWCHAR));
1011+
1012+
dest[wchar_capacity - 1] = L'\0';
1013+
1014+
status_record =
1015+
StatusRecord{SQLStates::k_01004(),
1016+
"Data truncated"};
1017+
1018+
} else {
1019+
1020+
status_record =
1021+
StatusRecord{SQLStates::k_22003(),
1022+
"Buffer length is insufficient"};
1023+
}
1024+
1025+
return status_record;
1026+
}
1027+
9381028
odbc_internal::StatusRecord ConvertFromTimestampDSValue(
9391029
DSValue const& src_dsval, DataBuffer& dest_data) {
940-
using odbc_internal::SQLStates;
941-
using odbc_internal::StatusRecord;
942-
using odbc_internal::StatusRecordOr;
9431030

944-
SQL_TIMESTAMP_STRUCT timestamp_src_struct;
945-
DSValueToTimestamp(src_dsval, timestamp_src_struct);
1031+
using odbc_internal::SQLStates;
1032+
using odbc_internal::StatusRecord;
9461033

947-
std::string timestamp_src_str;
948-
timestamp_src_str = FormatTimestampToString(timestamp_src_struct);
949-
950-
SQLSMALLINT dest_type = dest_data.type;
951-
SQLPOINTER dest_buf = dest_data.buf;
952-
SQLLEN buffer_length = dest_data.buflen;
953-
SQLLEN* res_len = dest_data.result_len;
1034+
std::string str_val;
1035+
DSValueToString(src_dsval, str_val);
1036+
std::string timestamp_src_str;
9541037

955-
// Define length variables
956-
int k_timestamp_src_len = timestamp_src_str.length();
957-
constexpr int kTimestampBinaryLength = sizeof(SQL_TIMESTAMP_STRUCT);
1038+
SQLSMALLINT dest_type = dest_data.type;
1039+
SQLPOINTER dest_buf = dest_data.buf;
1040+
SQLLEN buffer_length = dest_data.buflen;
1041+
SQLLEN* res_len = dest_data.result_len;
9581042

9591043
if (!dest_buf) {
9601044
return StatusRecord::Ok();
@@ -965,82 +1049,76 @@ odbc_internal::StatusRecord ConvertFromTimestampDSValue(
9651049
return StatusRecord{SQLStates::k_HY090(), "Invalid Buffer length"};
9661050
}
9671051

968-
StatusRecord status_record = StatusRecord::Ok();
1052+
StatusRecord status_record = StatusRecord::Ok();
9691053

970-
switch (dest_type) {
971-
case SQL_C_CHAR: {
972-
auto* dest = reinterpret_cast<char*>(dest_buf);
973-
if (buffer_length > k_timestamp_src_len) {
974-
if (res_len) {
975-
*res_len = k_timestamp_src_len;
1054+
// --- Check for ISO string with 'T' and long fraction ---
1055+
auto t_pos = str_val.find('T');
1056+
if (t_pos != std::string::npos) {
1057+
auto dot_pos = str_val.find('.', t_pos);
1058+
bool long_fraction = false;
1059+
1060+
if (dot_pos != std::string::npos) {
1061+
std::size_t fraction_length = str_val.size() - dot_pos - 1;
1062+
if (!str_val.empty() && str_val.back() == 'Z') --fraction_length;
1063+
if (fraction_length > 9) long_fraction = true;
9761064
}
977-
std::strncpy(dest, timestamp_src_str.c_str(), k_timestamp_src_len);
978-
dest[k_timestamp_src_len] = '\0';
979-
} else if (20 <= buffer_length && buffer_length <= k_timestamp_src_len) {
980-
if (res_len) {
981-
*res_len = buffer_length;
1065+
1066+
if (long_fraction) {
1067+
// Trim 'Z' and replace 'T' with space
1068+
if (!str_val.empty() && str_val.back() == 'Z') str_val.pop_back();
1069+
str_val[t_pos] = ' ';
1070+
timestamp_src_str = str_val;
1071+
1072+
switch (dest_type) {
1073+
case SQL_C_CHAR:
1074+
return ConvertTimestampStringToChar(timestamp_src_str, dest_buf, buffer_length, res_len);
1075+
case SQL_C_WCHAR:
1076+
return ConvertTimestampStringToWChar(timestamp_src_str, dest_buf, buffer_length, res_len);
1077+
default:
1078+
LOG(ERROR) << "ConvertFromTimestampDSValue:: Conversion unsupported for picosecond for C-type: " << dest_type;
1079+
return StatusRecord{SQLStates::k_HY000(), "Conversion unsupported for picosecond"};
1080+
}
9821081
}
983-
std::strncpy(dest, timestamp_src_str.c_str(), buffer_length - 1);
984-
dest[buffer_length - 1] = '\0';
985-
LOG(WARNING)
986-
<< "ConvertFromTimestampDSValue:: Data truncated for SQL_C_CHAR.";
987-
status_record = StatusRecord{SQLStates::k_01004(), "Data truncated"};
988-
} else {
989-
LOG(ERROR) << "ConvertFromTimestampDSValue:: Buffer length is "
990-
"insufficient for SQL_C_CHAR.";
991-
status_record =
992-
StatusRecord{SQLStates::k_22003(), "Buffer length is insufficient"};
993-
}
994-
break;
9951082
}
1083+
//Conversion for unix epoch time
1084+
bool looks_like_float_epoch = false;
9961085

997-
case SQL_C_WCHAR: {
998-
StatusRecordOr<std::wstring> wstr = Utf8ToUtf16(timestamp_src_str);
999-
if (!wstr) {
1000-
LOG(ERROR)
1001-
<< "ConvertFromTimestampDSValue:: DSValueToWchar Conversion Failed";
1002-
status_record = StatusRecord{SQLStates::k_HY000(),
1003-
"DSValueToWchar Conversion Failed"};
1004-
break;
1005-
}
1006-
std::vector<SQLWCHAR> wstr_data(wstr->begin(), wstr->end());
1007-
wstr_data.emplace_back(L'\0');
1086+
try {
1087+
size_t idx = 0;
1088+
std::stod(str_val, &idx);
1089+
if (idx == str_val.length()) {
1090+
looks_like_float_epoch = true;
1091+
}
1092+
} catch (...) {
1093+
looks_like_float_epoch = false;
1094+
}
1095+
SQL_TIMESTAMP_STRUCT timestamp_src_struct ;
1096+
if (looks_like_float_epoch) {
1097+
timestamp_src_str = FloatTimestampToString(str_val);
1098+
timestamp_src_struct= ConvertStrToTimestampStruct(timestamp_src_str);
1099+
} else {
1100+
DSValueToTimestamp(src_dsval, timestamp_src_struct);
1101+
timestamp_src_str =
1102+
FormatTimestampToString(timestamp_src_struct);
1103+
}
1104+
int k_timestamp_src_len = static_cast<int>(timestamp_src_str.length());
1105+
constexpr int kTimestampBinaryLength = sizeof(SQL_TIMESTAMP_STRUCT);
10081106

1009-
auto* dest = reinterpret_cast<SQLWCHAR*>(dest_buf);
1010-
SQLLEN wchar_capacity = buffer_length / sizeof(SQLWCHAR);
1011-
if (wchar_capacity > k_timestamp_src_len) {
1012-
if (res_len) {
1013-
*res_len = k_timestamp_src_len * sizeof(SQLWCHAR);
1014-
}
1015-
std::memcpy(dest, wstr_data.data(),
1016-
(k_timestamp_src_len) * sizeof(SQLWCHAR));
1017-
dest[k_timestamp_src_len] = L'\0';
1018-
} else if (20 <= wchar_capacity &&
1019-
wchar_capacity <= k_timestamp_src_len) {
1020-
if (res_len) {
1021-
*res_len = wchar_capacity * sizeof(SQLWCHAR);
1022-
}
1023-
std::memcpy(dest, wstr_data.data(),
1024-
(wchar_capacity) * sizeof(SQLWCHAR));
1025-
dest[wchar_capacity - 1] = L'\0';
1026-
LOG(WARNING)
1027-
<< "ConvertFromTimestampDSValue:: Data truncated for SQL_C_WCHAR.";
1028-
status_record = StatusRecord{SQLStates::k_01004(), "Data truncated"};
1029-
} else {
1030-
LOG(ERROR) << "ConvertFromTimestampDSValue:: Buffer length is "
1031-
"insufficient for SQL_C_WCHAR.";
1032-
status_record =
1033-
StatusRecord{SQLStates::k_22003(), "Buffer length is insufficient"};
1034-
}
1107+
switch (dest_type) {
1108+
case SQL_C_CHAR:
1109+
status_record = ConvertTimestampStringToChar(timestamp_src_str, dest_buf, buffer_length, res_len);
1110+
break;
1111+
1112+
case SQL_C_WCHAR:
1113+
status_record = ConvertTimestampStringToWChar(timestamp_src_str, dest_buf, buffer_length, res_len);
10351114
break;
1036-
}
10371115

10381116
case SQL_C_BINARY: {
10391117
if (kTimestampBinaryLength <= buffer_length) {
10401118
if (res_len) {
10411119
*res_len = kTimestampBinaryLength;
10421120
}
1043-
timestamp_src_struct.fraction = timestamp_src_struct.fraction * 1000;
1121+
timestamp_src_struct.fraction = timestamp_src_struct.fraction;
10441122
std::memcpy(dest_buf, &timestamp_src_struct, kTimestampBinaryLength);
10451123

10461124
} else {
@@ -1214,7 +1292,7 @@ odbc_internal::StatusRecord ConvertFromDatetimeDSValue(DSValue const& src_dsval,
12141292
if (res_len) {
12151293
*res_len = kDatetimeBinaryLength;
12161294
}
1217-
datetime_src_struct.fraction = datetime_src_struct.fraction * 1000;
1295+
datetime_src_struct.fraction = datetime_src_struct.fraction;
12181296
std::memcpy(dest_buf, &datetime_src_struct, kDatetimeBinaryLength);
12191297

12201298
} else {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,7 @@ TEST(ConvertFromTimestampDSValue, convertToBinarySuccess) {
11051105
expected_timestamp.hour = 01;
11061106
expected_timestamp.minute = 59;
11071107
expected_timestamp.second = 43;
1108-
expected_timestamp.fraction = 112233000;
1108+
expected_timestamp.fraction = 112233;
11091109
char dest_buf[30];
11101110
DataBuffer dest_data = {SQL_C_BINARY, dest_buf, sizeof(dest_buf),
11111111
&result_len};

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ void ConnectionHandle::SetUp(Section& dsn_section,
160160
std::string job_creation_mode = dsn_section["JOBCREATIONMODE"];
161161
dsn_.is_job_creation_required = (job_creation_mode == "1");
162162

163+
std::string timestamp_output_format =dsn_section["TIMESTAMPOUTPUTFORMAT"];
164+
if (!timestamp_output_format.empty()) {
165+
dsn_.format_options.timestamp_output_format = timestamp_output_format;
166+
}
167+
168+
163169
if (attribute_str_values_.count(SQL_ATTR_CURRENT_CATALOG) == 0) {
164170
attribute_str_values_.insert({SQL_ATTR_CURRENT_CATALOG, dsn_.catalog});
165171
}

google/cloud/odbc/bq_driver/internal/odbc_conn_handle.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ struct Dsn {
7373
std::vector<ConnectionProperty> connection_properties;
7474
std::uint32_t row_fetched_per_block = 100000;
7575
std::uint32_t default_string_column_length = 16384;
76+
google::cloud::bigquery_v2_minimal_internal::DataFormatOptions format_options;
7677
/////////////////////////////////////////////////////////////////
7778
// Optional Properties needed for HTAPI.
7879
/////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)