diff --git a/cpp/src/arrow/flight/sql/odbc/entry_points.cc b/cpp/src/arrow/flight/sql/odbc/entry_points.cc index 722a3975186f..c7bbc08e49e2 100644 --- a/cpp/src/arrow/flight/sql/odbc/entry_points.cc +++ b/cpp/src/arrow/flight/sql/odbc/entry_points.cc @@ -135,6 +135,15 @@ SQLRETURN SQL_API SQLExecDirect(SQLHSTMT stmt, SQLWCHAR* queryText, return arrow::SQLExecDirect(stmt, queryText, textLength); } +SQLRETURN SQL_API SQLFetch(SQLHSTMT stmt) { return arrow::SQLFetch(stmt); } + +SQLRETURN SQL_API SQLGetData(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, + SQLLEN* indicatorPtr) { + return arrow::SQLGetData(stmt, recordNumber, cType, dataPtr, bufferLength, + indicatorPtr); +} + SQLRETURN SQL_API SQLBindCol(SQLHSTMT statementHandle, SQLUSMALLINT columnNumber, SQLSMALLINT targetType, SQLPOINTER targetValuePtr, SQLLEN bufferLength, SQLLEN* strLen_or_IndPtr) { @@ -204,11 +213,6 @@ SQLRETURN SQL_API SQLExecute(SQLHSTMT statementHandle) { return SQL_ERROR; } -SQLRETURN SQL_API SQLFetch(SQLHSTMT statementHandle) { - LOG_DEBUG("SQLFetch called with statementHandle: {}", statementHandle); - return SQL_ERROR; -} - SQLRETURN SQL_API SQLForeignKeys(SQLHSTMT statementHandle, SQLWCHAR* pKCatalogName, SQLSMALLINT pKCatalogNameLength, SQLWCHAR* pKSchemaName, SQLSMALLINT pKSchemaNameLength, SQLWCHAR* pKTableName, @@ -232,27 +236,13 @@ SQLRETURN SQL_API SQLForeignKeys(SQLHSTMT statementHandle, SQLWCHAR* pKCatalogNa return SQL_ERROR; } -SQLRETURN SQL_API SQLGetData(SQLHSTMT statementHandle, SQLUSMALLINT col_or_Param_Num, - SQLSMALLINT targetType, SQLPOINTER targetValuePtr, - SQLLEN bufferLength, SQLLEN* strLen_or_IndPtr) { - LOG_DEBUG( - "SQLGetData called with statementHandle: {}, col_or_Param_Num: {}, targetType: {}, " - "targetValuePtr: {}, bufferLength: {}, strLen_or_IndPtr: {}", - statementHandle, col_or_Param_Num, targetType, targetValuePtr, bufferLength, - fmt::ptr(strLen_or_IndPtr)); - return SQL_ERROR; -} - SQLRETURN SQL_API SQLGetTypeInfo(SQLHSTMT statementHandle, SQLSMALLINT dataType) { LOG_DEBUG("SQLGetTypeInfoW called with statementHandle: {} dataType: {}", statementHandle, dataType); return SQL_ERROR; } -SQLRETURN SQL_API SQLMoreResults(SQLHSTMT statementHandle) { - LOG_DEBUG("SQLMoreResults called with statementHandle: {}", statementHandle); - return SQL_ERROR; -} +SQLRETURN SQL_API SQLMoreResults(SQLHSTMT stmt) { return arrow::SQLMoreResults(stmt); } SQLRETURN SQL_API SQLNativeSql(SQLHDBC connectionHandle, SQLWCHAR* inStatementText, SQLINTEGER inStatementTextLength, @@ -267,11 +257,12 @@ SQLRETURN SQL_API SQLNativeSql(SQLHDBC connectionHandle, SQLWCHAR* inStatementTe return SQL_ERROR; } -SQLRETURN SQL_API SQLNumResultCols(SQLHSTMT statementHandle, - SQLSMALLINT* columnCountPtr) { - LOG_DEBUG("SQLNumResultCols called with statementHandle: {}, columnCountPtr: {}", - statementHandle, fmt::ptr(columnCountPtr)); - return SQL_ERROR; +SQLRETURN SQL_API SQLNumResultCols(SQLHSTMT stmt, SQLSMALLINT* columnCountPtr) { + return arrow::SQLNumResultCols(stmt, columnCountPtr); +} + +SQLRETURN SQL_API SQLRowCount(SQLHSTMT stmt, SQLLEN* rowCountPtr) { + return arrow::SQLRowCount(stmt, rowCountPtr); } SQLRETURN SQL_API SQLPrepare(SQLHSTMT statementHandle, SQLWCHAR* statementText, diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/date_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/date_array_accessor_test.cc index d0c4d65099b2..769e87ce9e35 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/date_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/date_array_accessor_test.cc @@ -32,13 +32,16 @@ using arrow::NumericArray; using odbcabstraction::DATE_STRUCT; using odbcabstraction::OdbcVersion; -using odbcabstraction::tagDATE_STRUCT; using arrow::ArrayFromVector; using odbcabstraction::GetTimeForSecondsSinceEpoch; TEST(DateArrayAccessor, Test_Date32Array_CDataType_DATE) { - std::vector values = {7589, 12320, 18980, 19095}; + std::vector values = {7589, 12320, 18980, 19095, -1, 0}; + std::vector expected = { + {1990, 10, 12}, {2003, 9, 25}, {2021, 12, 19}, + {2022, 4, 13}, {1969, 12, 31}, {1970, 1, 1}, + }; std::shared_ptr array; ArrayFromVector(values, &array); @@ -46,7 +49,7 @@ TEST(DateArrayAccessor, Test_Date32Array_CDataType_DATE) { DateArrayFlightSqlAccessor accessor( dynamic_cast*>(array.get())); - std::vector buffer(values.size()); + std::vector buffer(values.size()); std::vector strlen_buffer(values.size()); ColumnBinding binding(odbcabstraction::CDataType_DATE, 0, 0, buffer.data(), 0, @@ -60,19 +63,31 @@ TEST(DateArrayAccessor, Test_Date32Array_CDataType_DATE) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(DATE_STRUCT), strlen_buffer[i]); - tm date{}; - int64_t converted_time = values[i] * 86400; - GetTimeForSecondsSinceEpoch(date, converted_time); - ASSERT_EQ((date.tm_year + 1900), buffer[i].year); - ASSERT_EQ(date.tm_mon + 1, buffer[i].month); - ASSERT_EQ(date.tm_mday, buffer[i].day); + ASSERT_EQ(expected[i].year, buffer[i].year); + ASSERT_EQ(expected[i].month, buffer[i].month); + ASSERT_EQ(expected[i].day, buffer[i].day); } } TEST(DateArrayAccessor, Test_Date64Array_CDataType_DATE) { - std::vector values = {86400000, 172800000, 259200000, 1649793238110, - 345600000, 432000000, 518400000}; + std::vector values = {86400000, 172800000, 259200000, 1649793238110, + 0, 345600000, 432000000, 518400000, + -86400000, -17987443200000}; + std::vector expected = { + /* year(16), month(u16), day(u16) */ + {1970, 1, 2}, + {1970, 1, 3}, + {1970, 1, 4}, + {2022, 4, 12}, + {1970, 1, 1}, + {1970, 1, 5}, + {1970, 1, 6}, + {1970, 1, 7}, + {1969, 12, 31}, + // This is the documented lower limit of supported Gregorian dates for boost + {1400, 1, 1}, + }; std::shared_ptr array; ArrayFromVector(values, &array); @@ -80,7 +95,7 @@ TEST(DateArrayAccessor, Test_Date64Array_CDataType_DATE) { DateArrayFlightSqlAccessor accessor( dynamic_cast*>(array.get())); - std::vector buffer(values.size()); + std::vector buffer(values.size()); std::vector strlen_buffer(values.size()); ColumnBinding binding(odbcabstraction::CDataType_DATE, 0, 0, buffer.data(), 0, @@ -96,11 +111,9 @@ TEST(DateArrayAccessor, Test_Date64Array_CDataType_DATE) { ASSERT_EQ(sizeof(DATE_STRUCT), strlen_buffer[i]); tm date{}; - int64_t converted_time = values[i] / 1000; - GetTimeForSecondsSinceEpoch(date, converted_time); - ASSERT_EQ((date.tm_year + 1900), buffer[i].year); - ASSERT_EQ(date.tm_mon + 1, buffer[i].month); - ASSERT_EQ(date.tm_mday, buffer[i].day); + ASSERT_EQ(expected[i].year, buffer[i].year); + ASSERT_EQ(expected[i].month, buffer[i].month); + ASSERT_EQ(expected[i].day, buffer[i].day); } } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.cc index b85cb95a88af..e1e1b7404925 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.cc @@ -18,10 +18,13 @@ #include "arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/calendar_utils.h" +#include +#include + using arrow::TimeUnit; namespace { -int64_t GetConversionToSecondsDivisor(TimeUnit::type unit) { +inline int64_t GetConversionToSecondsDivisor(TimeUnit::type unit) { int64_t divisor = 1; switch (unit) { case TimeUnit::SECOND: @@ -44,25 +47,26 @@ int64_t GetConversionToSecondsDivisor(TimeUnit::type unit) { return divisor; } -uint32_t CalculateFraction(TimeUnit::type unit, uint64_t units_since_epoch) { +uint32_t CalculateFraction(TimeUnit::type unit, int64_t units_since_epoch) { // Convert the given remainder and time unit to nanoseconds // since the fraction field on TIMESTAMP_STRUCT is in nanoseconds. - switch (unit) { - case TimeUnit::SECOND: - return 0; - case TimeUnit::MILLI: - // 1000000 nanoseconds = 1 millisecond. - return (units_since_epoch % driver::odbcabstraction::MILLI_TO_SECONDS_DIVISOR) * - 1000000; - case TimeUnit::MICRO: - // 1000 nanoseconds = 1 microsecond. - return (units_since_epoch % driver::odbcabstraction::MICRO_TO_SECONDS_DIVISOR) * - 1000; - case TimeUnit::NANO: - // 1000 nanoseconds = 1 microsecond. - return (units_since_epoch % driver::odbcabstraction::NANO_TO_SECONDS_DIVISOR); - } - return 0; + if (unit == TimeUnit::SECOND) return 0; + + const int64_t divisor = GetConversionToSecondsDivisor(unit); + const int64_t nano_divisor = GetConversionToSecondsDivisor(TimeUnit::NANO); + + if (units_since_epoch < 0) + if (units_since_epoch <= + (std::numeric_limits::min() + divisor)) + // Prevent trying to derive and add a value larger than INT64_MAX (i.e. the time + // value at the start of the second which is used to shift the value positive before + // the modulo operation)) in next statement. + units_since_epoch += divisor; + // See below regarding floor division; here we want ceiling division. + // FIXME this goes poorly (trying to use a value > INT64_MAX when units_since_epoch is + // less than the smallest multiple of divisor greater than INT64_MIN. + units_since_epoch += divisor * std::abs((units_since_epoch - (divisor - 1)) / divisor); + return static_cast((units_since_epoch % divisor) * (nano_divisor / divisor)); } } // namespace @@ -84,11 +88,25 @@ RowStatus TimestampArrayFlightSqlAccessor::MoveSingleCell_imp ColumnBinding* binding, int64_t arrow_row, int64_t cell_counter, int64_t& value_offset, bool update_value_offset, odbcabstraction::Diagnostics& diagnostics) { + // Times less than the minimum integer number of seconds that can be represented + // for each time unit will not convert correctly. This is mostly interesting for + // nanoseconds as timestamps in other units are outside of the accepted range of + // Gregorian dates. auto* buffer = static_cast(binding->buffer); int64_t value = this->GetArray()->Value(arrow_row); const auto divisor = GetConversionToSecondsDivisor(UNIT); - const auto converted_result_seconds = value / divisor; + const auto converted_result_seconds = + // We want floor division here; C++ will round towards zero + (value < 0) + // Floor division: Shift all "fractional" (not a multiple of divisor) values so + // they round towards zero (and to the same value) along with the "floor" less + // than them, then add 1 to get back to the floor. Althernative we could shift + // negatively by (divisor - 1) but this breaks near INT64_MIN causing + // underflow.. + ? ((value + 1) / divisor) - 1 + // Towards zero is already floor + : value / divisor; tm timestamp = {0}; GetTimeForSecondsSinceEpoch(timestamp, converted_result_seconds); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor_test.cc index a5fb167e79d1..10860239e877 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor_test.cc @@ -34,8 +34,28 @@ using odbcabstraction::TIMESTAMP_STRUCT; using odbcabstraction::GetTimeForSecondsSinceEpoch; TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MILLI) { - std::vector values = {86400370, 172800000, 259200000, 1649793238110LL, - 345600000, 432000000, 518400000}; + std::vector values = { + 86400370, 172800000, 259200000, 1649793238110LL, 345600000, 432000000, 518400000, + -86399000, 0, -86399999, -86399001, 86400001, 86400999}; + std::vector expected = { + /* year(16), month(u16), day(u16), hour(u16), minute(u16), second(u16), + fraction(u32) */ + {1970, 1, 2, 0, 0, 0, 370000000}, + {1970, 1, 3, 0, 0, 0, 0}, + {1970, 1, 4, 0, 0, 0, 0}, + {2022, 4, 12, 19, 53, 58, 110000000}, + {1970, 1, 5, 0, 0, 0, 0}, + {1970, 1, 6, 0, 0, 0, 0}, + {1970, 1, 7, 0, 0, 0, 0}, + {1969, 12, 31, 0, 0, 1, 0}, + {1970, 1, 1, 0, 0, 0, 0}, + /* Tests both ends of the fraction rounding range to ensure we don't tip the wrong + way */ + {1969, 12, 31, 0, 0, 0, 1000000}, + {1969, 12, 31, 0, 0, 0, 999000000}, + {1970, 1, 2, 0, 0, 0, 1000000}, + {1970, 1, 2, 0, 0, 0, 999000000}, + }; std::shared_ptr timestamp_array; @@ -60,28 +80,26 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MILLI) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(TIMESTAMP_STRUCT), strlen_buffer[i]); - tm date{}; - - auto converted_time = values[i] / odbcabstraction::MILLI_TO_SECONDS_DIVISOR; - GetTimeForSecondsSinceEpoch(date, converted_time); - - ASSERT_EQ(buffer[i].year, 1900 + (date.tm_year)); - ASSERT_EQ(buffer[i].month, date.tm_mon + 1); - ASSERT_EQ(buffer[i].day, date.tm_mday); - ASSERT_EQ(buffer[i].hour, date.tm_hour); - ASSERT_EQ(buffer[i].minute, date.tm_min); - ASSERT_EQ(buffer[i].second, date.tm_sec); - - constexpr uint32_t NANOSECONDS_PER_MILLI = 1000000; - ASSERT_EQ( - buffer[i].fraction, - (values[i] % odbcabstraction::MILLI_TO_SECONDS_DIVISOR) * NANOSECONDS_PER_MILLI); + ASSERT_EQ(buffer[i].year, expected[i].year); + ASSERT_EQ(buffer[i].month, expected[i].month); + ASSERT_EQ(buffer[i].day, expected[i].day); + ASSERT_EQ(buffer[i].hour, expected[i].hour); + ASSERT_EQ(buffer[i].minute, expected[i].minute); + ASSERT_EQ(buffer[i].second, expected[i].second); + ASSERT_EQ(buffer[i].fraction, expected[i].fraction); } } TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_SECONDS) { - std::vector values = {86400, 172800, 259200, 1649793238, - 345600, 432000, 518400}; + std::vector values = {86400, 172800, 259200, 1649793238, 345600, + 432000, 518400, -86399, 0}; + std::vector expected = { + /* year(16), month(u16), day(u16), hour(u16), minute(u16), second(u16), + fraction(u32) */ + {1970, 1, 2, 0, 0, 0, 0}, {1970, 1, 3, 0, 0, 0, 0}, {1970, 1, 4, 0, 0, 0, 0}, + {2022, 4, 12, 19, 53, 58, 0}, {1970, 1, 5, 0, 0, 0, 0}, {1970, 1, 6, 0, 0, 0, 0}, + {1970, 1, 7, 0, 0, 0, 0}, {1969, 12, 31, 0, 0, 1, 0}, {1970, 1, 1, 0, 0, 0, 0}, + }; std::shared_ptr timestamp_array; @@ -106,23 +124,26 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_SECONDS) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(TIMESTAMP_STRUCT), strlen_buffer[i]); - tm date{}; - - auto converted_time = values[i]; - GetTimeForSecondsSinceEpoch(date, converted_time); - - ASSERT_EQ(buffer[i].year, 1900 + (date.tm_year)); - ASSERT_EQ(buffer[i].month, date.tm_mon + 1); - ASSERT_EQ(buffer[i].day, date.tm_mday); - ASSERT_EQ(buffer[i].hour, date.tm_hour); - ASSERT_EQ(buffer[i].minute, date.tm_min); - ASSERT_EQ(buffer[i].second, date.tm_sec); + ASSERT_EQ(buffer[i].year, expected[i].year); + ASSERT_EQ(buffer[i].month, expected[i].month); + ASSERT_EQ(buffer[i].day, expected[i].day); + ASSERT_EQ(buffer[i].hour, expected[i].hour); + ASSERT_EQ(buffer[i].minute, expected[i].minute); + ASSERT_EQ(buffer[i].second, expected[i].second); ASSERT_EQ(buffer[i].fraction, 0); } } TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MICRO) { - std::vector values = {86400000000, 1649793238000000}; + std::vector values = {0, 86400000000, 1649793238000000, -86399999999, + -86399000001}; + std::vector expected = { + /* year(16), month(u16), day(u16), hour(u16), minute(u16), second(u16), + fraction(u32) */ + {1970, 1, 1, 0, 0, 0, 0}, {1970, 1, 2, 0, 0, 0, 0}, + {2022, 4, 12, 19, 53, 58, 0}, {1969, 12, 31, 0, 0, 0, 1000}, + {1969, 12, 31, 0, 0, 0, 999999000}, + }; std::shared_ptr timestamp_array; @@ -148,25 +169,39 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MICRO) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(TIMESTAMP_STRUCT), strlen_buffer[i]); - tm date{}; - - auto converted_time = values[i] / odbcabstraction::MICRO_TO_SECONDS_DIVISOR; - GetTimeForSecondsSinceEpoch(date, converted_time); - - ASSERT_EQ(buffer[i].year, 1900 + (date.tm_year)); - ASSERT_EQ(buffer[i].month, date.tm_mon + 1); - ASSERT_EQ(buffer[i].day, date.tm_mday); - ASSERT_EQ(buffer[i].hour, date.tm_hour); - ASSERT_EQ(buffer[i].minute, date.tm_min); - ASSERT_EQ(buffer[i].second, date.tm_sec); - constexpr uint32_t MICROS_PER_NANO = 1000; - ASSERT_EQ(buffer[i].fraction, - (values[i] % odbcabstraction::MICRO_TO_SECONDS_DIVISOR) * MICROS_PER_NANO); + ASSERT_EQ(buffer[i].year, expected[i].year); + ASSERT_EQ(buffer[i].month, expected[i].month); + ASSERT_EQ(buffer[i].day, expected[i].day); + ASSERT_EQ(buffer[i].hour, expected[i].hour); + ASSERT_EQ(buffer[i].minute, expected[i].minute); + ASSERT_EQ(buffer[i].second, expected[i].second); + ASSERT_EQ(buffer[i].fraction, expected[i].fraction); } } TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_NANO) { - std::vector values = {86400000010000, 1649793238000000000}; + std::vector values = {86400000010000, + 1649793238000000000, + -86399999999999, + -86399000000001, + 86400000000001, + 86400999999999, + 0, + -9223372036000000001}; + std::vector expected = { + /* year(16), month(u16), day(u16), hour(u16), minute(u16), second(u16), + fraction(u32) */ + {1970, 1, 2, 0, 0, 0, 10000}, + {2022, 4, 12, 19, 53, 58, 0}, + {1969, 12, 31, 0, 0, 0, 1}, + {1969, 12, 31, 0, 0, 0, 999999999}, + {1970, 1, 2, 0, 0, 0, 1}, + {1970, 1, 2, 0, 0, 0, 999999999}, + {1970, 1, 1, 0, 0, 0, 0}, + /* Test within range where floor (seconds) value is below INT64_MIN in nanoseconds + */ + {1677, 9, 21, 0, 12, 43, 999999999}, + }; std::shared_ptr timestamp_array; @@ -191,18 +226,14 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_NANO) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(TIMESTAMP_STRUCT), strlen_buffer[i]); - tm date{}; - - auto converted_time = values[i] / odbcabstraction::NANO_TO_SECONDS_DIVISOR; - GetTimeForSecondsSinceEpoch(date, converted_time); - - ASSERT_EQ(buffer[i].year, 1900 + (date.tm_year)); - ASSERT_EQ(buffer[i].month, date.tm_mon + 1); - ASSERT_EQ(buffer[i].day, date.tm_mday); - ASSERT_EQ(buffer[i].hour, date.tm_hour); - ASSERT_EQ(buffer[i].minute, date.tm_min); - ASSERT_EQ(buffer[i].second, date.tm_sec); - ASSERT_EQ(buffer[i].fraction, (values[i] % odbcabstraction::NANO_TO_SECONDS_DIVISOR)); + + ASSERT_EQ(buffer[i].year, expected[i].year); + ASSERT_EQ(buffer[i].month, expected[i].month); + ASSERT_EQ(buffer[i].day, expected[i].day); + ASSERT_EQ(buffer[i].hour, expected[i].hour); + ASSERT_EQ(buffer[i].minute, expected[i].minute); + ASSERT_EQ(buffer[i].second, expected[i].second); + ASSERT_EQ(buffer[i].fraction, expected[i].fraction); } } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.cc index 824260a68687..6744537ae5be 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.cc @@ -18,6 +18,8 @@ #include "arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" +#include + #include #include "arrow/flight/types.h" #include "arrow/scalar.h" @@ -226,14 +228,14 @@ void FlightSqlResultSet::Cancel() { current_chunk_.data = nullptr; } -bool FlightSqlResultSet::GetData(int column_n, int16_t target_type, int precision, - int scale, void* buffer, size_t buffer_length, - ssize_t* strlen_buffer) { +SQLRETURN FlightSqlResultSet::GetData(int column_n, int16_t target_type, int precision, + int scale, void* buffer, size_t buffer_length, + ssize_t* strlen_buffer) { reset_get_data_ = true; // Check if the offset is already at the end. int64_t& value_offset = get_data_offsets_[column_n - 1]; if (value_offset == -1) { - return false; + return SQL_NO_DATA; } ColumnBinding binding(ConvertCDataTypeFromV2ToV3(target_type), precision, scale, buffer, @@ -249,7 +251,11 @@ bool FlightSqlResultSet::GetData(int column_n, int16_t target_type, int precisio diagnostics_, nullptr); // If there was truncation, the converter would have reported it to the diagnostics. - return diagnostics_.HasWarning(); + if (diagnostics_.HasWarning()) { + return SQL_SUCCESS_WITH_INFO; + } else { + return SQL_SUCCESS; + } } std::shared_ptr FlightSqlResultSet::GetMetadata() { return metadata_; } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h index d1f20979a240..c82a2d835435 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h @@ -73,8 +73,8 @@ class FlightSqlResultSet : public ResultSet { void Cancel() override; - bool GetData(int column_n, int16_t target_type, int precision, int scale, void* buffer, - size_t buffer_length, ssize_t* strlen_buffer) override; + SQLRETURN GetData(int column_n, int16_t target_type, int precision, int scale, + void* buffer, size_t buffer_length, ssize_t* strlen_buffer) override; size_t Move(size_t rows, size_t bind_offset, size_t bind_type, uint16_t* row_status_array) override; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc.def b/cpp/src/arrow/flight/sql/odbc/odbc.def index 925109a5a9ff..d5767c5b5c4f 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc.def +++ b/cpp/src/arrow/flight/sql/odbc/odbc.def @@ -47,6 +47,7 @@ EXPORTS SQLGetInfoW SQLGetStmtAttrW SQLGetTypeInfoW + SQLRowCount SQLMoreResults SQLNativeSqlW SQLNumResultCols diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index 4053375b430c..508cb4122dac 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -25,6 +25,7 @@ #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/encoding_utils.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_environment.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h" @@ -340,7 +341,7 @@ SQLRETURN SQLGetDiagField(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLINT case SQL_DIAG_ROW_COUNT: { if (handleType == SQL_HANDLE_STMT) { if (diagInfoPtr) { - // Will always be 0 if only select supported + // Will always be 0 if only SELECT is supported *static_cast(diagInfoPtr) = 0; } @@ -905,4 +906,76 @@ SQLRETURN SQLExecDirect(SQLHSTMT stmt, SQLWCHAR* queryText, SQLINTEGER textLengt return SQL_SUCCESS; }); } + +SQLRETURN SQLFetch(SQLHSTMT stmt) { + LOG_DEBUG("SQLFetch called with stmt: {}", stmt); + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + + // The SQL_ATTR_ROW_ARRAY_SIZE statement attribute specifies the number of rows in the + // rowset. + ODBCDescriptor* ard = statement->GetARD(); + size_t rows = static_cast(ard->GetArraySize()); + if (statement->Fetch(rows)) { + return SQL_SUCCESS; + } else { + // Reached the end of rowset + return SQL_NO_DATA; + } + }); +} + +SQLRETURN SQLGetData(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, SQLLEN* indicatorPtr) { + // GH-46979: support SQL_C_GUID data type + // GH-46980: support Interval data types + // GH-46985: return warning message instead of error on float truncation case + LOG_DEBUG( + "SQLGetData called with stmt: {}, recordNumber: {}, cType: {}, " + "dataPtr: {}, bufferLength: {}, indicatorPtr: {}", + stmt, recordNumber, cType, dataPtr, bufferLength, fmt::ptr(indicatorPtr)); + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + return statement->GetData(recordNumber, cType, dataPtr, bufferLength, indicatorPtr); + }); +} + +SQLRETURN SQLMoreResults(SQLHSTMT stmt) { + LOG_DEBUG("SQLMoreResults called with stmt: {}", stmt); + // TODO: write tests for SQLMoreResults + using ODBC::ODBCStatement; + // Multiple result sets not supported. Return SQL_NO_DATA by default. + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + return statement->getMoreResults(); + }); +} + +SQLRETURN SQLNumResultCols(SQLHSTMT stmt, SQLSMALLINT* columnCountPtr) { + LOG_DEBUG("SQLNumResultCols called with stmt: {}, columnCountPtr: {}", stmt, + fmt::ptr(columnCountPtr)); + // TODO: write tests for SQLNumResultCols + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + statement->getColumnCount(columnCountPtr); + return SQL_SUCCESS; + }); +} + +SQLRETURN SQL_API SQLRowCount(SQLHSTMT stmt, SQLLEN* rowCountPtr) { + LOG_DEBUG("SQLRowCount called with stmt: {}, columnCountPtr: {}", stmt, + fmt::ptr(rowCountPtr)); + // TODO: write tests for SQLRowCount + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + statement->getRowCount(rowCountPtr); + return SQL_SUCCESS; + }); +} + } // namespace arrow diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.h b/cpp/src/arrow/flight/sql/odbc/odbc_api.h index af8fc4056ffc..c41a913090cb 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.h @@ -63,4 +63,10 @@ SQLRETURN SQLGetInfo(SQLHDBC conn, SQLUSMALLINT infoType, SQLPOINTER infoValuePt SQLRETURN SQLGetStmtAttr(SQLHSTMT stmt, SQLINTEGER attribute, SQLPOINTER valuePtr, SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr); SQLRETURN SQLExecDirect(SQLHSTMT stmt, SQLWCHAR* queryText, SQLINTEGER textLength); +SQLRETURN SQLFetch(SQLHSTMT stmt); +SQLRETURN SQLGetData(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, SQLLEN* indicatorPtr); +SQLRETURN SQLMoreResults(SQLHSTMT stmt); +SQLRETURN SQLNumResultCols(SQLHSTMT stmt, SQLSMALLINT* columnCountPtr); +SQLRETURN SQLRowCount(SQLHSTMT stmt, SQLLEN* rowCountPtr); } // namespace arrow diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/calendar_utils.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/calendar_utils.cc index f4a23419f1ab..d81487866adb 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/calendar_utils.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/calendar_utils.cc @@ -17,6 +17,7 @@ #include "odbcabstraction/calendar_utils.h" +#include #include #include @@ -40,12 +41,9 @@ int64_t GetTodayTimeFromEpoch() { } void GetTimeForSecondsSinceEpoch(tm& date, int64_t value) { -#if defined(_WIN32) - gmtime_s(&date, &value); -#else - time_t time_value = static_cast(value); - gmtime_r(&time_value, &date); -#endif + // Boost date-time library only support years from range 1400-9999 + // GH-46978: support years before 1400 for date, time, and timestamp types + date = boost::posix_time::to_tm(boost::posix_time::from_time_t(value)); } } // namespace odbcabstraction } // namespace driver diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h index 29efaec82803..df5ca5e34abe 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h @@ -83,8 +83,20 @@ class ODBCStatement : public ODBCHandle { inline SQLULEN GetRowsetSize() { return m_rowsetSize; } - bool GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, SQLPOINTER dataPtr, - SQLLEN bufferLength, SQLLEN* indicatorPtr); + SQLRETURN GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, SQLPOINTER dataPtr, + SQLLEN bufferLength, SQLLEN* indicatorPtr); + + SQLRETURN getMoreResults(); + + /** + * @brief Get number of columns from data set + */ + void getColumnCount(SQLSMALLINT* columnCountPtr); + + /** + * @brief Get number of rows affected by an UPDATE, INSERT, or DELETE statement + */ + void getRowCount(SQLLEN* rowCountPtr); /** * @brief Closes the cursor. This does _not_ un-prepare the statement or change diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h index 4c12a4b59348..c24c64248608 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h @@ -24,6 +24,8 @@ #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" +#include + namespace driver { namespace odbcabstraction { @@ -88,10 +90,10 @@ class ResultSet { /// \param buffer Target buffer to be populated. /// \param buffer_length Target buffer length. /// \param strlen_buffer Buffer that holds the length of value being fetched. - /// \returns true if there is more data to fetch from the current cell; - /// false if the whole value was already fetched. - virtual bool GetData(int column, int16_t target_type, int precision, int scale, - void* buffer, size_t buffer_length, ssize_t* strlen_buffer) = 0; + /// \returns SQLRETURN for SQLGetData. + virtual SQLRETURN GetData(int column, int16_t target_type, int precision, int scale, + void* buffer, size_t buffer_length, + ssize_t* strlen_buffer) = 0; }; } // namespace odbcabstraction diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc index e5ac4f414084..49ad527bab0d 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc @@ -129,6 +129,9 @@ SQLSMALLINT getCTypeForSQLType(const DescriptorRecord& record) { case SQL_WLONGVARCHAR: return SQL_C_WCHAR; + case SQL_BIT: + return SQL_C_BIT; + case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: @@ -146,13 +149,20 @@ SQLSMALLINT getCTypeForSQLType(const DescriptorRecord& record) { case SQL_BIGINT: return record.m_unsigned ? SQL_C_UBIGINT : SQL_C_SBIGINT; + case SQL_NUMERIC: + case SQL_DECIMAL: + return SQL_C_NUMERIC; + + case SQL_FLOAT: case SQL_REAL: return SQL_C_FLOAT; - case SQL_FLOAT: case SQL_DOUBLE: return SQL_C_DOUBLE; + case SQL_GUID: + return SQL_C_GUID; + case SQL_DATE: case SQL_TYPE_DATE: return SQL_C_TYPE_DATE; @@ -693,9 +703,9 @@ void ODBCStatement::closeCursor(bool suppressErrors) { m_hasReachedEndOfResult = false; } -bool ODBCStatement::GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, - SQLPOINTER dataPtr, SQLLEN bufferLength, - SQLLEN* indicatorPtr) { +SQLRETURN ODBCStatement::GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, + SQLLEN* indicatorPtr) { if (recordNumber == 0) { throw DriverException("Bookmarks are not supported", "07009"); } else if (recordNumber > m_ird->GetRecords().size()) { @@ -737,6 +747,34 @@ bool ODBCStatement::GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, bufferLength, indicatorPtr); } +SQLRETURN ODBCStatement::getMoreResults() { + // Multiple result sets are not supported. + if (m_currenResult) { + return SQL_NO_DATA; + } else { + throw DriverException("Function sequence error", "HY010"); + } +} + +void ODBCStatement::getColumnCount(SQLSMALLINT* columnCountPtr) { + if (!columnCountPtr) { + // columnCountPtr is not valid, do nothing as ODBC spec does not mention this as an + // error + return; + } + size_t columnCount = m_currentArd->GetRecords().size(); + *columnCountPtr = static_cast(columnCount); +} + +void ODBCStatement::getRowCount(SQLLEN* rowCountPtr) { + if (!rowCountPtr) { + // rowCountPtr is not valid, do nothing as ODBC spec does not mention this as an error + return; + } + // Will always be -1 (number of rows unknown) if only SELECT is supported + *rowCountPtr = -1; +} + void ODBCStatement::releaseStatement() { closeCursor(true); m_connection.dropStatement(this); diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc index f20cde44136e..a8da14c6ecf2 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc @@ -112,6 +112,57 @@ std::string FlightSQLODBCRemoteTestBase::getInvalidConnectionString() { return connect_str; } +std::wstring FlightSQLODBCRemoteTestBase::getQueryAllDataTypes() { + std::wstring wsql = + LR"( SELECT + -- Numeric types + -128 as stiny_int_min, 127 as stiny_int_max, + 0 as utiny_int_min, 255 as utiny_int_max, + + -32768 as ssmall_int_min, 32767 as ssmall_int_max, + 0 as usmall_int_min, 65535 as usmall_int_max, + + CAST(-2147483648 AS INTEGER) AS sinteger_min, + CAST(2147483647 AS INTEGER) AS sinteger_max, + CAST(0 AS BIGINT) AS uinteger_min, + CAST(4294967295 AS BIGINT) AS uinteger_max, + + CAST(-9223372036854775808 AS BIGINT) AS sbigint_min, + CAST(9223372036854775807 AS BIGINT) AS sbigint_max, + CAST(0 AS BIGINT) AS ubigint_min, + --Use string to represent unsigned big int due to lack of support from + --remote test server + '18446744073709551615' AS ubigint_max, + + CAST(-999999999 AS DECIMAL(38, 0)) AS decimal_negative, + CAST(999999999 AS DECIMAL(38, 0)) AS decimal_positive, + + CAST(-3.40282347E38 AS FLOAT) AS float_min, CAST(3.40282347E38 AS FLOAT) AS float_max, + + CAST(-1.7976931348623157E308 AS DOUBLE) AS double_min, + CAST(1.7976931348623157E308 AS DOUBLE) AS double_max, + + --Boolean + CAST(false AS BOOLEAN) AS bit_false, + CAST(true AS BOOLEAN) AS bit_true, + + --Character types + 'Z' AS c_char, '你' AS c_wchar, + + '你好' AS c_wvarchar, + + 'XYZ' AS c_varchar, + + --Date / timestamp + CAST(DATE '1400-01-01' AS DATE) AS date_min, + CAST(DATE '9999-12-31' AS DATE) AS date_max, + + CAST(TIMESTAMP '1400-01-01 00:00:00' AS TIMESTAMP) AS timestamp_min, + CAST(TIMESTAMP '9999-12-31 23:59:59' AS TIMESTAMP) AS timestamp_max; + )"; + return wsql; +} + void FlightSQLODBCRemoteTestBase::SetUp() { if (arrow::internal::GetEnvVar(TEST_CONNECT_STR).ValueOr("").empty()) { GTEST_SKIP() << "Skipping FlightSQLODBCRemoteTestBase test: TEST_CONNECT_STR not set"; @@ -173,6 +224,55 @@ std::string FlightSQLODBCMockTestBase::getInvalidConnectionString() { return connect_str; } +std::wstring FlightSQLODBCMockTestBase::getQueryAllDataTypes() { + std::wstring wsql = + LR"( SELECT + -- Numeric types + -128 AS stiny_int_min, 127 AS stiny_int_max, + 0 AS utiny_int_min, 255 AS utiny_int_max, + + -32768 AS ssmall_int_min, 32767 AS ssmall_int_max, + 0 AS usmall_int_min, 65535 AS usmall_int_max, + + CAST(-2147483648 AS INTEGER) AS sinteger_min, + CAST(2147483647 AS INTEGER) AS sinteger_max, + CAST(0 AS INTEGER) AS uinteger_min, + CAST(4294967295 AS INTEGER) AS uinteger_max, + + CAST(-9223372036854775808 AS INTEGER) AS sbigint_min, + CAST(9223372036854775807 AS INTEGER) AS sbigint_max, + CAST(0 AS INTEGER) AS ubigint_min, + -- stored as TEXT as SQLite doesn't support unsigned big int + '18446744073709551615' AS ubigint_max, + + CAST('-999999999' AS NUMERIC) AS decimal_negative, + CAST('999999999' AS NUMERIC) AS decimal_positive, + + CAST(-3.40282347E38 AS REAL) AS float_min, + CAST(3.40282347E38 AS REAL) AS float_max, + + CAST(-1.7976931348623157E308 AS REAL) AS double_min, + CAST(1.7976931348623157E308 AS REAL) AS double_max, + + -- Boolean + 0 AS bit_false, + 1 AS bit_true, + + -- Character types + 'Z' AS c_char, + '你' AS c_wchar, + '你好' AS c_wvarchar, + 'XYZ' AS c_varchar, + + DATE('1400-01-01') AS date_min, + DATE('9999-12-31') AS date_max, + + DATETIME('1400-01-01 00:00:00') AS timestamp_min, + DATETIME('9999-12-31 23:59:59') AS timestamp_max; + )"; + return wsql; +} + void FlightSQLODBCMockTestBase::SetUp() { ASSERT_OK_AND_ASSIGN(auto location, Location::ForGrpcTcp("0.0.0.0", 0)); arrow::flight::FlightServerOptions options(location); diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h index 5f8b3b9c7577..fc9216c3e851 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h @@ -61,6 +61,8 @@ class FlightSQLODBCRemoteTestBase : public ::testing::Test { /// \brief Get invalid connection string based on connection string defined in /// environment variable "ARROW_FLIGHT_SQL_ODBC_CONN" std::string virtual getInvalidConnectionString(); + /// \brief Return a SQL query that selects all data types + std::wstring virtual getQueryAllDataTypes(); /** ODBC Environment. */ SQLHENV env; @@ -119,6 +121,8 @@ class FlightSQLODBCMockTestBase : public FlightSQLODBCRemoteTestBase { std::string getConnectionString() override; /// \brief Get invalid connection string for mock server std::string getInvalidConnectionString() override; + /// \brief Return a SQL query that selects all data types + std::wstring getQueryAllDataTypes() override; int port; @@ -151,8 +155,12 @@ bool compareConnPropertyMap(Connection::ConnPropertyMap map1, /// Get error message from ODBC driver using SQLGetDiagRec std::string GetOdbcErrorMessage(SQLSMALLINT handle_type, SQLHANDLE handle); +static constexpr std::string_view error_state_01004 = "01004"; +static constexpr std::string_view error_state_01S07 = "01S07"; static constexpr std::string_view error_state_01S02 = "01S02"; static constexpr std::string_view error_state_08003 = "08003"; +static constexpr std::string_view error_state_22002 = "22002"; +static constexpr std::string_view error_state_24000 = "24000"; static constexpr std::string_view error_state_28000 = "28000"; static constexpr std::string_view error_state_HY000 = "HY000"; static constexpr std::string_view error_state_HY024 = "HY024"; diff --git a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc index 5f9ee0607606..4c4a753e703d 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc @@ -24,6 +24,9 @@ #include #include +#include + +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace arrow::flight::sql::odbc { @@ -38,7 +41,26 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectSimpleQuery) { SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); EXPECT_EQ(ret, SQL_SUCCESS); - // TODO: after SQLFetch and SQLGetData are implemented, fetch data to verify + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Verify 1 is returned + EXPECT_EQ(val, 1); + + ret = SQLFetch(this->stmt); + + EXPECT_EQ(ret, SQL_NO_DATA); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + // Invalid cursor state + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_24000); this->disconnect(); } @@ -58,4 +80,1300 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectInvalidQuery) { this->disconnect(); } + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectDataQuery) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Numeric Types + + // Signed Tiny Int + int8_t stiny_int_val; + SQLLEN buf_len = sizeof(stiny_int_val); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_STINYINT, &stiny_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(stiny_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 2, SQL_C_STINYINT, &stiny_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(stiny_int_val, std::numeric_limits::max()); + + // Unsigned Tiny Int + uint8_t utiny_int_val; + buf_len = sizeof(utiny_int_val); + + ret = SQLGetData(this->stmt, 3, SQL_C_UTINYINT, &utiny_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(utiny_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 4, SQL_C_UTINYINT, &utiny_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(utiny_int_val, std::numeric_limits::max()); + + // Signed Small Int + int16_t ssmall_int_val; + buf_len = sizeof(ssmall_int_val); + + ret = SQLGetData(this->stmt, 5, SQL_C_SSHORT, &ssmall_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ssmall_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 6, SQL_C_SSHORT, &ssmall_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ssmall_int_val, std::numeric_limits::max()); + + // Unsigned Small Int + uint16_t usmall_int_val; + buf_len = sizeof(usmall_int_val); + + ret = SQLGetData(this->stmt, 7, SQL_C_USHORT, &usmall_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(usmall_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 8, SQL_C_USHORT, &usmall_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(usmall_int_val, std::numeric_limits::max()); + + // Signed Integer + SQLINTEGER slong_val; + buf_len = sizeof(slong_val); + + ret = SQLGetData(this->stmt, 9, SQL_C_SLONG, &slong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 10, SQL_C_SLONG, &slong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::max()); + + // Unsigned Integer + SQLUINTEGER ulong_val; + buf_len = sizeof(ulong_val); + + ret = SQLGetData(this->stmt, 11, SQL_C_ULONG, &ulong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ulong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 12, SQL_C_ULONG, &ulong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ulong_val, std::numeric_limits::max()); + + // Signed Big Int + SQLBIGINT sbig_int_val; + buf_len = sizeof(sbig_int_val); + + ret = SQLGetData(this->stmt, 13, SQL_C_SBIGINT, &sbig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 14, SQL_C_SBIGINT, &sbig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::max()); + + // Unsigned Big Int + SQLUBIGINT ubig_int_val; + buf_len = sizeof(ubig_int_val); + + ret = SQLGetData(this->stmt, 15, SQL_C_UBIGINT, &ubig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ubig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 16, SQL_C_UBIGINT, &ubig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ubig_int_val, std::numeric_limits::max()); + + // Decimal + SQL_NUMERIC_STRUCT decimal_val; + memset(&decimal_val, 0, sizeof(decimal_val)); + buf_len = sizeof(SQL_NUMERIC_STRUCT); + + ret = SQLGetData(this->stmt, 17, SQL_C_NUMERIC, &decimal_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for negative decimal_val value + EXPECT_EQ(decimal_val.sign, 0); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + memset(&decimal_val, 0, sizeof(decimal_val)); + ret = SQLGetData(this->stmt, 18, SQL_C_NUMERIC, &decimal_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for positive decimal_val value + EXPECT_EQ(decimal_val.sign, 1); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + // Float + float float_val; + buf_len = sizeof(float_val); + + ret = SQLGetData(this->stmt, 19, SQL_C_FLOAT, &float_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative float value + EXPECT_EQ(float_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 20, SQL_C_FLOAT, &float_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(float_val, std::numeric_limits::max()); + + // Double + SQLDOUBLE double_val; + buf_len = sizeof(double_val); + + ret = SQLGetData(this->stmt, 21, SQL_C_DOUBLE, &double_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative double value + EXPECT_EQ(double_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 22, SQL_C_DOUBLE, &double_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(double_val, std::numeric_limits::max()); + + // Bit + bool bit_val; + buf_len = sizeof(bit_val); + + ret = SQLGetData(this->stmt, 23, SQL_C_BIT, &bit_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, false); + + ret = SQLGetData(this->stmt, 24, SQL_C_BIT, &bit_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, true); + + // Characters + + // Char + SQLCHAR char_val[2]; + buf_len = sizeof(SQLCHAR) * 2; + + ret = SQLGetData(this->stmt, 25, SQL_C_CHAR, &char_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(char_val[0], 'Z'); + + // WChar + SQLWCHAR wchar_val[2]; + constexpr size_t wchar_size = driver::odbcabstraction::GetSqlWCharSize(); + buf_len = wchar_size * 2; + + ret = SQLGetData(this->stmt, 26, SQL_C_WCHAR, &wchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wchar_val[0], L'你'); + + // WVarchar + SQLWCHAR wvarchar_val[3]; + buf_len = wchar_size * 3; + + ret = SQLGetData(this->stmt, 27, SQL_C_WCHAR, &wvarchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wvarchar_val[0], L'你'); + EXPECT_EQ(wvarchar_val[1], L'好'); + + // varchar + SQLCHAR varchar_val[4]; + buf_len = sizeof(SQLCHAR) * 4; + + ret = SQLGetData(this->stmt, 28, SQL_C_CHAR, &varchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(varchar_val[0], 'X'); + EXPECT_EQ(varchar_val[1], 'Y'); + EXPECT_EQ(varchar_val[2], 'Z'); + + // Date and Timestamp + + // Date + SQL_DATE_STRUCT date_var{}; + buf_len = sizeof(date_var); + + ret = SQLGetData(this->stmt, 29, SQL_C_TYPE_DATE, &date_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(date_var.day, 1); + EXPECT_EQ(date_var.month, 1); + EXPECT_EQ(date_var.year, 1400); + + ret = SQLGetData(this->stmt, 30, SQL_C_TYPE_DATE, &date_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(date_var.day, 31); + EXPECT_EQ(date_var.month, 12); + EXPECT_EQ(date_var.year, 9999); + + // Timestamp + SQL_TIMESTAMP_STRUCT timestamp_var{}; + buf_len = sizeof(timestamp_var); + + ret = SQLGetData(this->stmt, 31, SQL_C_TYPE_TIMESTAMP, ×tamp_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(timestamp_var.day, 1); + EXPECT_EQ(timestamp_var.month, 1); + EXPECT_EQ(timestamp_var.year, 1400); + EXPECT_EQ(timestamp_var.hour, 0); + EXPECT_EQ(timestamp_var.minute, 0); + EXPECT_EQ(timestamp_var.second, 0); + EXPECT_EQ(timestamp_var.fraction, 0); + + ret = SQLGetData(this->stmt, 32, SQL_C_TYPE_TIMESTAMP, ×tamp_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(timestamp_var.day, 31); + EXPECT_EQ(timestamp_var.month, 12); + EXPECT_EQ(timestamp_var.year, 9999); + EXPECT_EQ(timestamp_var.hour, 23); + EXPECT_EQ(timestamp_var.minute, 59); + EXPECT_EQ(timestamp_var.second, 59); + EXPECT_EQ(timestamp_var.fraction, 0); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectTimeQuery) { + // Mock server test is skipped due to limitation on the mock server. + // Time type from mock server does not include the fraction + this->connect(); + + std::wstring wsql = + LR"( + SELECT CAST(TIME '00:00:00' AS TIME) AS time_min, + CAST(TIME '23:59:59' AS TIME) AS time_max; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQL_TIME_STRUCT time_var{}; + SQLLEN buf_len = sizeof(time_var); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_TYPE_TIME, &time_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for time. + EXPECT_EQ(time_var.hour, 0); + EXPECT_EQ(time_var.minute, 0); + EXPECT_EQ(time_var.second, 0); + + ret = SQLGetData(this->stmt, 2, SQL_C_TYPE_TIME, &time_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for time. + EXPECT_EQ(time_var.hour, 23); + EXPECT_EQ(time_var.minute, 59); + EXPECT_EQ(time_var.second, 59); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLExecDirectVarbinaryQuery) { + // Have binary test on mock test base as remote test servers tend to have different + // formats for binary data + this->connect(); + + std::wstring wsql = L"SELECT X'ABCDEF' AS c_varbinary;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // varbinary + std::vector varbinary_val(3); + SQLLEN buf_len = varbinary_val.size(); + SQLLEN ind; + ret = SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val[0], buf_len, &ind); + EXPECT_EQ(varbinary_val[0], '\xAB'); + EXPECT_EQ(varbinary_val[1], '\xCD'); + EXPECT_EQ(varbinary_val[2], '\xEF'); + + this->disconnect(); +} + +// Tests with SQL_C_DEFAULT as the target type + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectDataQueryDefaultType) { + // Test with default types. Only testing target types supported by server. + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Numeric Types + // Signed Integer + SQLINTEGER slong_val; + SQLLEN buf_len = sizeof(slong_val); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 9, SQL_C_DEFAULT, &slong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 10, SQL_C_DEFAULT, &slong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::max()); + + // Signed Big Int + SQLBIGINT sbig_int_val; + buf_len = sizeof(sbig_int_val); + + ret = SQLGetData(this->stmt, 13, SQL_C_DEFAULT, &sbig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 14, SQL_C_DEFAULT, &sbig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::max()); + + // Decimal + SQL_NUMERIC_STRUCT decimal_val; + memset(&decimal_val, 0, sizeof(decimal_val)); + buf_len = sizeof(SQL_NUMERIC_STRUCT); + + ret = SQLGetData(this->stmt, 17, SQL_C_DEFAULT, &decimal_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for negative decimal_val value + EXPECT_EQ(decimal_val.sign, 0); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + memset(&decimal_val, 0, sizeof(decimal_val)); + ret = SQLGetData(this->stmt, 18, SQL_C_DEFAULT, &decimal_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for positive decimal_val value + EXPECT_EQ(decimal_val.sign, 1); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + // Float + float float_val; + buf_len = sizeof(float_val); + + ret = SQLGetData(this->stmt, 19, SQL_C_DEFAULT, &float_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative float value + EXPECT_EQ(float_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 20, SQL_C_DEFAULT, &float_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(float_val, std::numeric_limits::max()); + + // Double + SQLDOUBLE double_val; + buf_len = sizeof(double_val); + + ret = SQLGetData(this->stmt, 21, SQL_C_DEFAULT, &double_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative double value + EXPECT_EQ(double_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 22, SQL_C_DEFAULT, &double_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(double_val, std::numeric_limits::max()); + + // Bit + bool bit_val; + buf_len = sizeof(bit_val); + + ret = SQLGetData(this->stmt, 23, SQL_C_DEFAULT, &bit_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, false); + + ret = SQLGetData(this->stmt, 24, SQL_C_DEFAULT, &bit_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, true); + + // Characters + + // Char will be fetched as wchar by default + SQLWCHAR wchar_val[2]; + constexpr size_t wchar_size = driver::odbcabstraction::GetSqlWCharSize(); + buf_len = wchar_size * 2; + + ret = SQLGetData(this->stmt, 25, SQL_C_DEFAULT, &wchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wchar_val[0], L'Z'); + + // WChar + SQLWCHAR wchar_val2[2]; + buf_len = wchar_size * 2; + ret = SQLGetData(this->stmt, 26, SQL_C_DEFAULT, &wchar_val2, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wchar_val2[0], L'你'); + + // WVarchar + SQLWCHAR wvarchar_val[3]; + buf_len = wchar_size * 3; + + ret = SQLGetData(this->stmt, 27, SQL_C_DEFAULT, &wvarchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wvarchar_val[0], L'你'); + EXPECT_EQ(wvarchar_val[1], L'好'); + + // Varchar will be fetched as WVarchar by default + SQLWCHAR wvarchar_val2[4]; + buf_len = wchar_size * 4; + + ret = SQLGetData(this->stmt, 28, SQL_C_DEFAULT, &wvarchar_val2, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wvarchar_val2[0], L'X'); + EXPECT_EQ(wvarchar_val2[1], L'Y'); + EXPECT_EQ(wvarchar_val2[2], L'Z'); + + // Date and Timestamp + + // Date + SQL_DATE_STRUCT date_var{}; + buf_len = sizeof(date_var); + + ret = SQLGetData(this->stmt, 29, SQL_C_DEFAULT, &date_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(date_var.day, 1); + EXPECT_EQ(date_var.month, 1); + EXPECT_EQ(date_var.year, 1400); + + ret = SQLGetData(this->stmt, 30, SQL_C_DEFAULT, &date_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(date_var.day, 31); + EXPECT_EQ(date_var.month, 12); + EXPECT_EQ(date_var.year, 9999); + + // Timestamp + SQL_TIMESTAMP_STRUCT timestamp_var{}; + buf_len = sizeof(timestamp_var); + + ret = SQLGetData(this->stmt, 31, SQL_C_DEFAULT, ×tamp_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(timestamp_var.day, 1); + EXPECT_EQ(timestamp_var.month, 1); + EXPECT_EQ(timestamp_var.year, 1400); + EXPECT_EQ(timestamp_var.hour, 0); + EXPECT_EQ(timestamp_var.minute, 0); + EXPECT_EQ(timestamp_var.second, 0); + EXPECT_EQ(timestamp_var.fraction, 0); + + ret = SQLGetData(this->stmt, 32, SQL_C_DEFAULT, ×tamp_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(timestamp_var.day, 31); + EXPECT_EQ(timestamp_var.month, 12); + EXPECT_EQ(timestamp_var.year, 9999); + EXPECT_EQ(timestamp_var.hour, 23); + EXPECT_EQ(timestamp_var.minute, 59); + EXPECT_EQ(timestamp_var.second, 59); + EXPECT_EQ(timestamp_var.fraction, 0); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectTimeQueryDefaultType) { + // Mock server test is skipped due to limitation on the mock server. + // Time type from mock server does not include the fraction + this->connect(); + + std::wstring wsql = + LR"( + SELECT CAST(TIME '00:00:00' AS TIME) AS time_min, + CAST(TIME '23:59:59' AS TIME) AS time_max; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQL_TIME_STRUCT time_var{}; + SQLLEN buf_len = sizeof(time_var); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_DEFAULT, &time_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for time. + EXPECT_EQ(time_var.hour, 0); + EXPECT_EQ(time_var.minute, 0); + EXPECT_EQ(time_var.second, 0); + + ret = SQLGetData(this->stmt, 2, SQL_C_DEFAULT, &time_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for time. + EXPECT_EQ(time_var.hour, 23); + EXPECT_EQ(time_var.minute, 59); + EXPECT_EQ(time_var.second, 59); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectVarbinaryQueryDefaultType) { + // Limitation on mock test server prevents SQL_C_DEFAULT from working properly. + // Mock server has type `DENSE_UNION` for varbinary. + // Note that not all remote servers support "from_hex" function + this->connect(); + + std::wstring wsql = L"SELECT from_hex('ABCDEF') AS c_varbinary;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // varbinary + std::vector varbinary_val(3); + SQLLEN buf_len = varbinary_val.size(); + SQLLEN ind; + ret = SQLGetData(this->stmt, 1, SQL_C_DEFAULT, &varbinary_val[0], buf_len, &ind); + EXPECT_EQ(varbinary_val[0], '\xAB'); + EXPECT_EQ(varbinary_val[1], '\xCD'); + EXPECT_EQ(varbinary_val[2], '\xEF'); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectGuidQueryUnsupported) { + this->connect(); + + // Query GUID as string as SQLite does not support GUID + std::wstring wsql = L"SELECT 'C77313CF-4E08-47CE-B6DF-94DD2FCF3541' AS guid;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLGUID guid_var; + SQLLEN buf_len = sizeof(guid_var); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_GUID, &guid_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_ERROR); + // GUID is not supported by ODBC + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY000); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectRowFetching) { + this->connect(); + + std::wstring wsql = + LR"( + SELECT 1 AS small_table + UNION ALL + SELECT 2 + UNION ALL + SELECT 3; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Fetch row 1 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + SQLLEN buf_len = sizeof(val); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Verify 1 is returned + EXPECT_EQ(val, 1); + + // Fetch row 2 + ret = SQLFetch(this->stmt); + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Verify 2 is returned + EXPECT_EQ(val, 2); + + // Fetch row 3 + ret = SQLFetch(this->stmt); + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Verify 3 is returned + EXPECT_EQ(val, 3); + + // Verify result set has no more data beyond row 3 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, &ind); + + EXPECT_EQ(ret, SQL_ERROR); + // Invalid cursor state + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_24000); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectVarcharTruncation) { + this->connect(); + + std::wstring wsql = L"SELECT 'VERY LONG STRING here' AS string_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + const int len = 17; + SQLCHAR char_val[len]; + SQLLEN buf_len = sizeof(SQLCHAR) * len; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + EXPECT_EQ(ODBC::SqlStringToString(char_val), std::string("VERY LONG STRING")); + EXPECT_EQ(ind, 21); + + // Fetch same column 2nd time + const int len2 = 2; + SQLCHAR char_val2[len2]; + buf_len = sizeof(SQLCHAR) * len2; + + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val2, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + EXPECT_EQ(ODBC::SqlStringToString(char_val2), std::string(" ")); + EXPECT_EQ(ind, 5); + + // Fetch same column 3rd time + const int len3 = 5; + SQLCHAR char_val3[len3]; + buf_len = sizeof(SQLCHAR) * len3; + + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val3, buf_len, &ind); + + // Verify that there is no more truncation reports. The full string has been fetched. + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(ODBC::SqlStringToString(char_val3), std::string("here")); + EXPECT_EQ(ind, 4); + + // Attempt to fetch data 4th time + SQLCHAR char_val4[len]; + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val4, 0, &ind); + // Verify SQL_NO_DATA is returned + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectWVarcharTruncation) { + this->connect(); + + std::wstring wsql = L"SELECT 'VERY LONG Unicode STRING 句子 here' AS wstring_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + const int len = 28; + SQLWCHAR wchar_val[len]; + constexpr size_t wchar_size = driver::odbcabstraction::GetSqlWCharSize(); + SQLLEN buf_len = wchar_size * len; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + EXPECT_EQ(std::wstring(wchar_val), std::wstring(L"VERY LONG Unicode STRING 句子")); + EXPECT_EQ(ind, 32 * wchar_size); + + // Fetch same column 2nd time + const int len2 = 2; + SQLWCHAR wchar_val2[len2]; + buf_len = wchar_size * len2; + + ret = SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val2, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + EXPECT_EQ(std::wstring(wchar_val2), std::wstring(L" ")); + EXPECT_EQ(ind, 5 * wchar_size); + + // Fetch same column 3rd time + const int len3 = 5; + SQLWCHAR wchar_val3[len3]; + buf_len = wchar_size * len3; + + ret = SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val3, buf_len, &ind); + + // Verify that there is no more truncation reports. The full string has been fetched. + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(std::wstring(wchar_val3), std::wstring(L"here")); + EXPECT_EQ(ind, 4 * wchar_size); + + // Attempt to fetch data 4th time + SQLWCHAR wchar_val4[len]; + ret = SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val4, 0, &ind); + // Verify SQL_NO_DATA is returned + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLExecDirectVarbinaryTruncation) { + // Have binary test on mock test base as remote test servers tend to have different + // formats for binary data + this->connect(); + + std::wstring wsql = L"SELECT X'ABCDEFAB' AS c_varbinary;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // varbinary + std::vector varbinary_val(3); + SQLLEN buf_len = varbinary_val.size(); + SQLLEN ind; + ret = SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val[0], buf_len, &ind); + // Verify binary truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + EXPECT_EQ(varbinary_val[0], '\xAB'); + EXPECT_EQ(varbinary_val[1], '\xCD'); + EXPECT_EQ(varbinary_val[2], '\xEF'); + EXPECT_EQ(ind, 4); + + // Fetch same column 2nd time + std::vector varbinary_val2(1); + buf_len = varbinary_val2.size(); + + ret = SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val2[0], buf_len, &ind); + + // Verify that there is no more truncation reports. The full binary has been fetched. + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(varbinary_val[0], '\xAB'); + EXPECT_EQ(ind, 1); + + // Attempt to fetch data 3rd time + std::vector varbinary_val3(1); + buf_len = varbinary_val3.size(); + ret = SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val3[0], buf_len, &ind); + // Verify SQL_NO_DATA is returned + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectFloatTruncation) { + // Test is disabled until float truncation is supported. + // GH-46985: return warning message instead of error on float truncation case + GTEST_SKIP(); + this->connect(); + + std::wstring wsql; + if constexpr (std::is_same_v) { + wsql = std::wstring(L"SELECT CAST(1.234 AS REAL) AS float_val"); + } else if constexpr (std::is_same_v) { + wsql = std::wstring(L"SELECT CAST(1.234 AS FLOAT) AS float_val"); + } + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + int16_t ssmall_int_val; + SQLLEN buf_len = sizeof(ssmall_int_val); + + ret = SQLGetData(this->stmt, 1, SQL_C_SSHORT, &ssmall_int_val, 0, 0); + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify float truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01S07); + + EXPECT_EQ(ssmall_int_val, 1); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectNullQuery) { + // Limitation on mock test server prevents null from working properly. + // Mock server has type `DENSE_UNION` for null column data. + this->connect(); + + std::wstring wsql = L"SELECT null as null_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify SQL_NULL_DATA is returned for indicator + EXPECT_EQ(ind, SQL_NULL_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLExecDirectTruncationQueryNullIndicator) { + // Driver should not error out when indicator is null if the cell is non-null + // Have binary test on mock test base as remote test servers tend to have different + // formats for binary data + this->connect(); + + std::wstring wsql = + LR"( + SELECT 1, + 'VERY LONG STRING here' AS string_col, + 'VERY LONG Unicode STRING 句子 here' AS wstring_col, + X'ABCDEFAB' AS c_varbinary; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Verify 1 is returned for non-truncation case. + EXPECT_EQ(val, 1); + + // Char + const int len = 17; + SQLCHAR char_val[len]; + SQLLEN buf_len = sizeof(SQLCHAR) * len; + + ret = SQLGetData(this->stmt, 2, SQL_C_CHAR, &char_val, buf_len, 0); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + // WChar + const int len2 = 28; + SQLWCHAR wchar_val[len2]; + constexpr size_t wchar_size = driver::odbcabstraction::GetSqlWCharSize(); + buf_len = wchar_size * len2; + + ret = SQLGetData(this->stmt, 3, SQL_C_WCHAR, &wchar_val, buf_len, 0); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + // varbinary + std::vector varbinary_val(3); + buf_len = varbinary_val.size(); + ret = SQLGetData(this->stmt, 4, SQL_C_BINARY, &varbinary_val[0], buf_len, 0); + // Verify binary truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectNullQueryNullIndicator) { + // Limitation on mock test server prevents null from working properly. + // Mock server has type `DENSE_UNION` for null column data. + this->connect(); + + std::wstring wsql = L"SELECT null as null_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + // Verify invalid null indicator is reported, as it is required + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_22002); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectIgnoreInvalidBufLen) { + // Verify the driver ignores invalid buffer length for fixed data types + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Numeric Types + + // Signed Tiny Int + int8_t stiny_int_val; + SQLLEN invalid_buf_len = -1; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_STINYINT, &stiny_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(stiny_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 2, SQL_C_STINYINT, &stiny_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(stiny_int_val, std::numeric_limits::max()); + + // Unsigned Tiny Int + uint8_t utiny_int_val; + + ret = SQLGetData(this->stmt, 3, SQL_C_UTINYINT, &utiny_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(utiny_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 4, SQL_C_UTINYINT, &utiny_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(utiny_int_val, std::numeric_limits::max()); + + // Signed Small Int + int16_t ssmall_int_val; + + ret = SQLGetData(this->stmt, 5, SQL_C_SSHORT, &ssmall_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ssmall_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 6, SQL_C_SSHORT, &ssmall_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ssmall_int_val, std::numeric_limits::max()); + + // Unsigned Small Int + uint16_t usmall_int_val; + + ret = SQLGetData(this->stmt, 7, SQL_C_USHORT, &usmall_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(usmall_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 8, SQL_C_USHORT, &usmall_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(usmall_int_val, std::numeric_limits::max()); + + // Signed Integer + SQLINTEGER slong_val; + + ret = SQLGetData(this->stmt, 9, SQL_C_SLONG, &slong_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 10, SQL_C_SLONG, &slong_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::max()); + + // Unsigned Integer + SQLUINTEGER ulong_val; + + ret = SQLGetData(this->stmt, 11, SQL_C_ULONG, &ulong_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ulong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 12, SQL_C_ULONG, &ulong_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ulong_val, std::numeric_limits::max()); + + // Signed Big Int + SQLBIGINT sbig_int_val; + + ret = SQLGetData(this->stmt, 13, SQL_C_SBIGINT, &sbig_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 14, SQL_C_SBIGINT, &sbig_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::max()); + + // Unsigned Big Int + SQLUBIGINT ubig_int_val; + + ret = SQLGetData(this->stmt, 15, SQL_C_UBIGINT, &ubig_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ubig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 16, SQL_C_UBIGINT, &ubig_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ubig_int_val, std::numeric_limits::max()); + + // Decimal + SQL_NUMERIC_STRUCT decimal_val; + memset(&decimal_val, 0, sizeof(decimal_val)); + + ret = SQLGetData(this->stmt, 17, SQL_C_NUMERIC, &decimal_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for negative decimal_val value + EXPECT_EQ(decimal_val.sign, 0); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + memset(&decimal_val, 0, sizeof(decimal_val)); + ret = SQLGetData(this->stmt, 18, SQL_C_NUMERIC, &decimal_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for positive decimal_val value + EXPECT_EQ(decimal_val.sign, 1); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + // Float + float float_val; + + ret = SQLGetData(this->stmt, 19, SQL_C_FLOAT, &float_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative float value + EXPECT_EQ(float_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 20, SQL_C_FLOAT, &float_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(float_val, std::numeric_limits::max()); + + // Double + SQLDOUBLE double_val; + + ret = SQLGetData(this->stmt, 21, SQL_C_DOUBLE, &double_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative double value + EXPECT_EQ(double_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 22, SQL_C_DOUBLE, &double_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(double_val, std::numeric_limits::max()); + + // Bit + bool bit_val; + + ret = SQLGetData(this->stmt, 23, SQL_C_BIT, &bit_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, false); + + ret = SQLGetData(this->stmt, 24, SQL_C_BIT, &bit_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, true); + + // Date and Timestamp + + // Date + SQL_DATE_STRUCT date_var{}; + + ret = SQLGetData(this->stmt, 29, SQL_C_TYPE_DATE, &date_var, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(date_var.day, 1); + EXPECT_EQ(date_var.month, 1); + EXPECT_EQ(date_var.year, 1400); + + ret = SQLGetData(this->stmt, 30, SQL_C_TYPE_DATE, &date_var, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(date_var.day, 31); + EXPECT_EQ(date_var.month, 12); + EXPECT_EQ(date_var.year, 9999); + + // Timestamp + SQL_TIMESTAMP_STRUCT timestamp_var{}; + + ret = SQLGetData(this->stmt, 31, SQL_C_TYPE_TIMESTAMP, ×tamp_var, invalid_buf_len, + &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(timestamp_var.day, 1); + EXPECT_EQ(timestamp_var.month, 1); + EXPECT_EQ(timestamp_var.year, 1400); + EXPECT_EQ(timestamp_var.hour, 0); + EXPECT_EQ(timestamp_var.minute, 0); + EXPECT_EQ(timestamp_var.second, 0); + EXPECT_EQ(timestamp_var.fraction, 0); + + ret = SQLGetData(this->stmt, 32, SQL_C_TYPE_TIMESTAMP, ×tamp_var, invalid_buf_len, + &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(timestamp_var.day, 31); + EXPECT_EQ(timestamp_var.month, 12); + EXPECT_EQ(timestamp_var.year, 9999); + EXPECT_EQ(timestamp_var.hour, 23); + EXPECT_EQ(timestamp_var.minute, 59); + EXPECT_EQ(timestamp_var.second, 59); + EXPECT_EQ(timestamp_var.fraction, 0); + + this->disconnect(); +} + } // namespace arrow::flight::sql::odbc