This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 1f599f484c GH-47713: [C++][FlightRPC] ODBC Basic Data Retrieval
(#48034)
1f599f484c is described below
commit 1f599f484c393a24418d302762cf472ba2a70f12
Author: Alina (Xi) Li <[email protected]>
AuthorDate: Thu Dec 4 22:40:45 2025 -0800
GH-47713: [C++][FlightRPC] ODBC Basic Data Retrieval (#48034)
### Rationale for this change
Add implementation for ODBC basic data fetching and retrieval
### What changes are included in this PR?
- SQLFetch & SQLGetData implementation, data retrieval bug fixes under
`odbc_impl`
- tests
### Are these changes tested?
Tested on local MSVC.
### Are there any user-facing changes?
N/A
* GitHub Issue: #47713
Authored-by: Alina (Xi) Li <[email protected]>
Signed-off-by: David Li <[email protected]>
---
cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 30 +-
.../sql/odbc/odbc_impl/flight_sql_result_set.cc | 16 +-
.../sql/odbc/odbc_impl/flight_sql_result_set.h | 4 +-
.../flight/sql/odbc/odbc_impl/odbc_statement.cc | 70 +-
.../flight/sql/odbc/odbc_impl/odbc_statement.h | 8 +-
.../flight/sql/odbc/odbc_impl/spi/result_set.h | 10 +-
.../arrow/flight/sql/odbc/tests/statement_test.cc | 1076 +++++++++++++++++++-
7 files changed, 1158 insertions(+), 56 deletions(-)
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index c4604f8aa2..270da0de4a 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -1052,8 +1052,24 @@ SQLRETURN SQLExecute(SQLHSTMT stmt) {
SQLRETURN SQLFetch(SQLHSTMT stmt) {
ARROW_LOG(DEBUG) << "SQLFetch called with stmt: " << stmt;
- // GH-47713 TODO: Implement SQLFetch
- return SQL_INVALID_HANDLE;
+
+ using ODBC::ODBCDescriptor;
+ using ODBC::ODBCStatement;
+ return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
+ ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
+
+ // The SQL_ATTR_ROW_ARRAY_SIZE statement attribute specifies the number of
rows in the
+ // rowset. Retrieve it with GetArraySize.
+ ODBCDescriptor* ard = statement->GetARD();
+ size_t rows = static_cast<size_t>(ard->GetArraySize());
+
+ if (statement->Fetch(rows)) {
+ return SQL_SUCCESS;
+ } else {
+ // Reached the end of rowset
+ return SQL_NO_DATA;
+ }
+ });
}
SQLRETURN SQLExtendedFetch(SQLHSTMT stmt, SQLUSMALLINT fetch_orientation,
@@ -1113,8 +1129,14 @@ SQLRETURN SQLGetData(SQLHSTMT stmt, SQLUSMALLINT
record_number, SQLSMALLINT c_ty
<< ", record_number: " << record_number << ", c_type: " <<
c_type
<< ", data_ptr: " << data_ptr << ", buffer_length: " <<
buffer_length
<< ", indicator_ptr: " << static_cast<const
void*>(indicator_ptr);
- // GH-47713 TODO: Implement SQLGetData
- return SQL_INVALID_HANDLE;
+
+ using ODBC::ODBCStatement;
+
+ return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
+ ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
+ return statement->GetData(record_number, c_type, data_ptr, buffer_length,
+ indicator_ptr);
+ });
}
SQLRETURN SQLMoreResults(SQLHSTMT stmt) {
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set.cc
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set.cc
index 80967b9f20..56e5bb973f 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set.cc
@@ -17,6 +17,8 @@
#include "arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set.h"
+#include <sql.h>
+
#include <utility>
#include "arrow/flight/types.h"
#include "arrow/scalar.h"
@@ -212,14 +214,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* str_len_buffer) {
+SQLRETURN FlightSqlResultSet::GetData(int column_n, int16_t target_type, int
precision,
+ int scale, void* buffer, size_t
buffer_length,
+ ssize_t* str_len_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(util::ConvertCDataTypeFromV2ToV3(target_type),
precision, scale,
@@ -235,7 +237,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<ResultSetMetadata> FlightSqlResultSet::GetMetadata() { return
metadata_; }
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set.h
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set.h
index ac2ae80e01..4f19b05bf2 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set.h
@@ -61,8 +61,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* str_len_buffer) override;
+ SQLRETURN GetData(int column_n, int16_t target_type, int precision, int
scale,
+ void* buffer, size_t buffer_length, ssize_t*
str_len_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_impl/odbc_statement.cc
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc
index aa490816b7..e54621c12e 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc
@@ -129,6 +129,9 @@ SQLSMALLINT getc_typeForSQLType(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 getc_typeForSQLType(const DescriptorRecord&
record) {
case SQL_BIGINT:
return record.is_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;
@@ -165,32 +175,32 @@ SQLSMALLINT getc_typeForSQLType(const DescriptorRecord&
record) {
case SQL_TYPE_TIMESTAMP:
return SQL_C_TYPE_TIMESTAMP;
- case SQL_C_INTERVAL_DAY:
- return SQL_INTERVAL_DAY;
- case SQL_C_INTERVAL_DAY_TO_HOUR:
- return SQL_INTERVAL_DAY_TO_HOUR;
- case SQL_C_INTERVAL_DAY_TO_MINUTE:
- return SQL_INTERVAL_DAY_TO_MINUTE;
- case SQL_C_INTERVAL_DAY_TO_SECOND:
- return SQL_INTERVAL_DAY_TO_SECOND;
- case SQL_C_INTERVAL_HOUR:
- return SQL_INTERVAL_HOUR;
- case SQL_C_INTERVAL_HOUR_TO_MINUTE:
- return SQL_INTERVAL_HOUR_TO_MINUTE;
- case SQL_C_INTERVAL_HOUR_TO_SECOND:
- return SQL_INTERVAL_HOUR_TO_SECOND;
- case SQL_C_INTERVAL_MINUTE:
- return SQL_INTERVAL_MINUTE;
- case SQL_C_INTERVAL_MINUTE_TO_SECOND:
- return SQL_INTERVAL_MINUTE_TO_SECOND;
- case SQL_C_INTERVAL_SECOND:
- return SQL_INTERVAL_SECOND;
- case SQL_C_INTERVAL_YEAR:
- return SQL_INTERVAL_YEAR;
- case SQL_C_INTERVAL_YEAR_TO_MONTH:
- return SQL_INTERVAL_YEAR_TO_MONTH;
- case SQL_C_INTERVAL_MONTH:
- return SQL_INTERVAL_MONTH;
+ case SQL_INTERVAL_DAY:
+ return SQL_C_INTERVAL_DAY;
+ case SQL_INTERVAL_DAY_TO_HOUR:
+ return SQL_C_INTERVAL_DAY_TO_HOUR;
+ case SQL_INTERVAL_DAY_TO_MINUTE:
+ return SQL_C_INTERVAL_DAY_TO_MINUTE;
+ case SQL_INTERVAL_DAY_TO_SECOND:
+ return SQL_C_INTERVAL_DAY_TO_SECOND;
+ case SQL_INTERVAL_HOUR:
+ return SQL_C_INTERVAL_HOUR;
+ case SQL_INTERVAL_HOUR_TO_MINUTE:
+ return SQL_C_INTERVAL_HOUR_TO_MINUTE;
+ case SQL_INTERVAL_HOUR_TO_SECOND:
+ return SQL_C_INTERVAL_HOUR_TO_SECOND;
+ case SQL_INTERVAL_MINUTE:
+ return SQL_C_INTERVAL_MINUTE;
+ case SQL_INTERVAL_MINUTE_TO_SECOND:
+ return SQL_C_INTERVAL_MINUTE_TO_SECOND;
+ case SQL_INTERVAL_SECOND:
+ return SQL_C_INTERVAL_SECOND;
+ case SQL_INTERVAL_YEAR:
+ return SQL_C_INTERVAL_YEAR;
+ case SQL_INTERVAL_YEAR_TO_MONTH:
+ return SQL_C_INTERVAL_YEAR_TO_MONTH;
+ case SQL_INTERVAL_MONTH:
+ return SQL_C_INTERVAL_MONTH;
default:
throw DriverException("Unknown SQL type: " +
std::to_string(record.concise_type),
@@ -707,9 +717,9 @@ void ODBCStatement::CloseCursor(bool suppress_errors) {
has_reached_end_of_result_ = false;
}
-bool ODBCStatement::GetData(SQLSMALLINT record_number, SQLSMALLINT c_type,
- SQLPOINTER data_ptr, SQLLEN buffer_length,
- SQLLEN* indicator_ptr) {
+SQLRETURN ODBCStatement::GetData(SQLSMALLINT record_number, SQLSMALLINT c_type,
+ SQLPOINTER data_ptr, SQLLEN buffer_length,
+ SQLLEN* indicator_ptr) {
if (record_number == 0) {
throw DriverException("Bookmarks are not supported", "07009");
} else if (record_number > ird_->GetRecords().size()) {
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h
index 2409c17d29..c90b9a87d2 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h
@@ -58,9 +58,7 @@ class ODBCStatement : public ODBCHandle<ODBCStatement> {
void ExecutePrepared();
void ExecuteDirect(const std::string& query);
- /**
- * @brief Returns true if the number of rows fetch was greater than zero.
- */
+ /// \brief Return true if the number of rows fetch was greater than zero.
bool Fetch(size_t rows);
bool IsPrepared() const;
@@ -77,8 +75,8 @@ class ODBCStatement : public ODBCHandle<ODBCStatement> {
inline SQLULEN GetRowsetSize() { return rowset_size_; }
- bool GetData(SQLSMALLINT record_number, SQLSMALLINT c_type, SQLPOINTER
data_ptr,
- SQLLEN buffer_length, SQLLEN* indicator_ptr);
+ SQLRETURN GetData(SQLSMALLINT record_number, SQLSMALLINT c_type, SQLPOINTER
data_ptr,
+ SQLLEN buffer_length, SQLLEN* indicator_ptr);
/// \brief Closes the cursor. This does _not_ un-prepare the statement or
change
/// bindings.
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set.h
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set.h
index a273d62f63..120e6132f1 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set.h
@@ -24,6 +24,8 @@
#include "arrow/flight/sql/odbc/odbc_impl/types.h"
+#include <sqltypes.h>
+
namespace arrow::flight::sql::odbc {
class ResultSetMetadata;
@@ -87,10 +89,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;
+ /// \return 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 arrow::flight::sql::odbc
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 4dbe87ddbb..869cdd987f 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
@@ -44,8 +44,6 @@ TYPED_TEST(StatementTest, TestSQLExecDirectSimpleQuery) {
ASSERT_EQ(SQL_SUCCESS,
SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
- // GH-47713 TODO: Uncomment call to SQLFetch SQLGetData after implementation
- /*
ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
SQLINTEGER val;
@@ -59,7 +57,6 @@ TYPED_TEST(StatementTest, TestSQLExecDirectSimpleQuery) {
ASSERT_EQ(SQL_ERROR, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0));
// Invalid cursor state
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState24000);
- */
}
TYPED_TEST(StatementTest, TestSQLExecDirectInvalidQuery) {
@@ -81,8 +78,6 @@ TYPED_TEST(StatementTest, TestSQLExecuteSimpleQuery) {
ASSERT_EQ(SQL_SUCCESS, SQLExecute(this->stmt));
- // GH-47713 TODO: Uncomment call to SQLFetch SQLGetData after implementation
- /*
// Fetch data
ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
@@ -97,7 +92,6 @@ TYPED_TEST(StatementTest, TestSQLExecuteSimpleQuery) {
ASSERT_EQ(SQL_ERROR, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0));
// Invalid cursor state
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState24000);
- */
}
TYPED_TEST(StatementTest, TestSQLPrepareInvalidQuery) {
@@ -114,6 +108,1076 @@ TYPED_TEST(StatementTest, TestSQLPrepareInvalidQuery) {
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY010);
}
+TYPED_TEST(StatementTest, TestSQLExecDirectDataQuery) {
+ std::wstring wsql = this->GetQueryAllDataTypes();
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ // Numeric Types
+
+ // Signed Tiny Int
+ int8_t stiny_int_val;
+ SQLLEN buf_len = sizeof(stiny_int_val);
+ SQLLEN ind;
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 1, SQL_C_STINYINT, &stiny_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<int8_t>::min(), stiny_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 2, SQL_C_STINYINT, &stiny_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<int8_t>::max(), stiny_int_val);
+
+ // Unsigned Tiny Int
+ uint8_t utiny_int_val;
+ buf_len = sizeof(utiny_int_val);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 3, SQL_C_UTINYINT, &utiny_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<uint8_t>::min(), utiny_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 4, SQL_C_UTINYINT, &utiny_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<uint8_t>::max(), utiny_int_val);
+
+ // Signed Small Int
+ int16_t ssmall_int_val;
+ buf_len = sizeof(ssmall_int_val);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 5, SQL_C_SSHORT, &ssmall_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<int16_t>::min(), ssmall_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 6, SQL_C_SSHORT, &ssmall_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<int16_t>::max(), ssmall_int_val);
+
+ // Unsigned Small Int
+ uint16_t usmall_int_val;
+ buf_len = sizeof(usmall_int_val);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 7, SQL_C_USHORT, &usmall_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<uint16_t>::min(), usmall_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 8, SQL_C_USHORT, &usmall_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(), usmall_int_val);
+
+ // Signed Integer
+ SQLINTEGER slong_val;
+ buf_len = sizeof(slong_val);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 9, SQL_C_SLONG, &slong_val, buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLINTEGER>::min(), slong_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 10, SQL_C_SLONG, &slong_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLINTEGER>::max(), slong_val);
+
+ // Unsigned Integer
+ SQLUINTEGER ulong_val;
+ buf_len = sizeof(ulong_val);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 11, SQL_C_ULONG, &ulong_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLUINTEGER>::min(), ulong_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 12, SQL_C_ULONG, &ulong_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLUINTEGER>::max(), ulong_val);
+
+ // Signed Big Int
+ SQLBIGINT sbig_int_val;
+ buf_len = sizeof(sbig_int_val);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 13, SQL_C_SBIGINT, &sbig_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLBIGINT>::min(), sbig_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 14, SQL_C_SBIGINT, &sbig_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLBIGINT>::max(), sbig_int_val);
+
+ // Unsigned Big Int
+ SQLUBIGINT ubig_int_val;
+ buf_len = sizeof(ubig_int_val);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 15, SQL_C_UBIGINT, &ubig_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLUBIGINT>::min(), ubig_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 16, SQL_C_UBIGINT, &ubig_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLUBIGINT>::max(), ubig_int_val);
+
+ // Decimal
+ SQL_NUMERIC_STRUCT decimal_val;
+ memset(&decimal_val, 0, sizeof(decimal_val));
+ buf_len = sizeof(SQL_NUMERIC_STRUCT);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 17, SQL_C_NUMERIC, &decimal_val, buf_len,
&ind));
+ // Check for negative decimal_val value
+ EXPECT_EQ(0, decimal_val.sign);
+ EXPECT_EQ(0, decimal_val.scale);
+ EXPECT_EQ(38, decimal_val.precision);
+ 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));
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 18, SQL_C_NUMERIC, &decimal_val, buf_len,
&ind));
+ // Check for positive decimal_val value
+ EXPECT_EQ(1, decimal_val.sign);
+ EXPECT_EQ(0, decimal_val.scale);
+ EXPECT_EQ(38, decimal_val.precision);
+ 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);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 19, SQL_C_FLOAT, &float_val, buf_len,
&ind));
+ // Get minimum negative float value
+ EXPECT_EQ(-std::numeric_limits<float>::max(), float_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 20, SQL_C_FLOAT, &float_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<float>::max(), float_val);
+
+ // Double
+ SQLDOUBLE double_val;
+ buf_len = sizeof(double_val);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 21, SQL_C_DOUBLE, &double_val, buf_len,
&ind));
+ // Get minimum negative double value
+ EXPECT_EQ(-std::numeric_limits<SQLDOUBLE>::max(), double_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 22, SQL_C_DOUBLE, &double_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLDOUBLE>::max(), double_val);
+
+ // Bit
+ bool bit_val;
+ buf_len = sizeof(bit_val);
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 23, SQL_C_BIT, &bit_val,
buf_len, &ind));
+ EXPECT_EQ(false, bit_val);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 24, SQL_C_BIT, &bit_val,
buf_len, &ind));
+ EXPECT_EQ(true, bit_val);
+
+ // Characters
+
+ // Char
+ SQLCHAR char_val[2];
+ buf_len = sizeof(SQLCHAR) * 2;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 25, SQL_C_CHAR, &char_val, buf_len, &ind));
+ EXPECT_EQ('Z', char_val[0]);
+
+ // WChar
+ SQLWCHAR wchar_val[2];
+ size_t wchar_size = GetSqlWCharSize();
+ buf_len = wchar_size * 2;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 26, SQL_C_WCHAR, &wchar_val, buf_len,
&ind));
+ EXPECT_EQ(L'你', wchar_val[0]);
+
+ // WVarchar
+ SQLWCHAR wvarchar_val[3];
+ buf_len = wchar_size * 3;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 27, SQL_C_WCHAR, &wvarchar_val, buf_len,
&ind));
+ EXPECT_EQ(L'你', wvarchar_val[0]);
+ EXPECT_EQ(L'好', wvarchar_val[1]);
+
+ // varchar
+ SQLCHAR varchar_val[4];
+ buf_len = sizeof(SQLCHAR) * 4;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 28, SQL_C_CHAR, &varchar_val, buf_len,
&ind));
+ EXPECT_EQ('X', varchar_val[0]);
+ EXPECT_EQ('Y', varchar_val[1]);
+ EXPECT_EQ('Z', varchar_val[2]);
+
+ // Date and Timestamp
+
+ // Date
+ SQL_DATE_STRUCT date_var{};
+ buf_len = sizeof(date_var);
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 29, SQL_C_TYPE_DATE, &date_var, buf_len,
&ind));
+ // Check min values for date. Min valid year is 1400.
+ EXPECT_EQ(1, date_var.day);
+ EXPECT_EQ(1, date_var.month);
+ EXPECT_EQ(1400, date_var.year);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 30, SQL_C_TYPE_DATE, &date_var, buf_len,
&ind));
+ // Check max values for date. Max valid year is 9999.
+ EXPECT_EQ(31, date_var.day);
+ EXPECT_EQ(12, date_var.month);
+ EXPECT_EQ(9999, date_var.year);
+
+ // Timestamp
+ SQL_TIMESTAMP_STRUCT timestamp_var{};
+ buf_len = sizeof(timestamp_var);
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 31, SQL_C_TYPE_TIMESTAMP,
×tamp_var,
+ buf_len, &ind));
+ // Check min values for date. Min valid year is 1400.
+ EXPECT_EQ(1, timestamp_var.day);
+ EXPECT_EQ(1, timestamp_var.month);
+ EXPECT_EQ(1400, timestamp_var.year);
+ EXPECT_EQ(0, timestamp_var.hour);
+ EXPECT_EQ(0, timestamp_var.minute);
+ EXPECT_EQ(0, timestamp_var.second);
+ EXPECT_EQ(0, timestamp_var.fraction);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 32, SQL_C_TYPE_TIMESTAMP,
×tamp_var,
+ buf_len, &ind));
+ // Check max values for date. Max valid year is 9999.
+ EXPECT_EQ(31, timestamp_var.day);
+ EXPECT_EQ(12, timestamp_var.month);
+ EXPECT_EQ(9999, timestamp_var.year);
+ EXPECT_EQ(23, timestamp_var.hour);
+ EXPECT_EQ(59, timestamp_var.minute);
+ EXPECT_EQ(59, timestamp_var.second);
+ EXPECT_EQ(0, timestamp_var.fraction);
+}
+
+TEST_F(StatementRemoteTest, TestSQLExecDirectTimeQuery) {
+ // Mock server test is skipped due to limitation on the mock server.
+ // Time type from mock server does not include the fraction
+
+ 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<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ SQL_TIME_STRUCT time_var{};
+ SQLLEN buf_len = sizeof(time_var);
+ SQLLEN ind;
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 1, SQL_C_TYPE_TIME, &time_var, buf_len,
&ind));
+ // Check min values for time.
+ EXPECT_EQ(0, time_var.hour);
+ EXPECT_EQ(0, time_var.minute);
+ EXPECT_EQ(0, time_var.second);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 2, SQL_C_TYPE_TIME, &time_var, buf_len,
&ind));
+ // Check max values for time.
+ EXPECT_EQ(23, time_var.hour);
+ EXPECT_EQ(59, time_var.minute);
+ EXPECT_EQ(59, time_var.second);
+}
+
+TEST_F(StatementMockTest, TestSQLExecDirectVarbinaryQuery) {
+ // Have binary test on mock test base as remote test servers tend to have
different
+ // formats for binary data
+
+ std::wstring wsql = L"SELECT X'ABCDEF' AS c_varbinary;";
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ // varbinary
+ std::vector<int8_t> varbinary_val(3);
+ SQLLEN buf_len = varbinary_val.size();
+ SQLLEN ind;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val[0],
buf_len, &ind));
+ EXPECT_EQ('\xAB', varbinary_val[0]);
+ EXPECT_EQ('\xCD', varbinary_val[1]);
+ EXPECT_EQ('\xEF', varbinary_val[2]);
+}
+
+// Tests with SQL_C_DEFAULT as the target type
+
+TEST_F(StatementRemoteTest, TestSQLExecDirectDataQueryDefaultType) {
+ // Test with default types. Only testing target types supported by server.
+
+ std::wstring wsql = this->GetQueryAllDataTypes();
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ // Numeric Types
+ // Signed Integer
+ SQLINTEGER slong_val;
+ SQLLEN buf_len = sizeof(slong_val);
+ SQLLEN ind;
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 9, SQL_C_DEFAULT, &slong_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLINTEGER>::min(), slong_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 10, SQL_C_DEFAULT, &slong_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLINTEGER>::max(), slong_val);
+
+ // Signed Big Int
+ SQLBIGINT sbig_int_val;
+ buf_len = sizeof(sbig_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 13, SQL_C_DEFAULT, &sbig_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLBIGINT>::min(), sbig_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 14, SQL_C_DEFAULT, &sbig_int_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLBIGINT>::max(), sbig_int_val);
+
+ // Decimal
+ SQL_NUMERIC_STRUCT decimal_val;
+ memset(&decimal_val, 0, sizeof(decimal_val));
+ buf_len = sizeof(SQL_NUMERIC_STRUCT);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 17, SQL_C_DEFAULT, &decimal_val, buf_len,
&ind));
+ // Check for negative decimal_val value
+ EXPECT_EQ(0, decimal_val.sign);
+ EXPECT_EQ(0, decimal_val.scale);
+ EXPECT_EQ(38, decimal_val.precision);
+ 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));
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 18, SQL_C_DEFAULT, &decimal_val, buf_len,
&ind));
+ // Check for positive decimal_val value
+ EXPECT_EQ(1, decimal_val.sign);
+ EXPECT_EQ(0, decimal_val.scale);
+ EXPECT_EQ(38, decimal_val.precision);
+ 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);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 19, SQL_C_DEFAULT, &float_val, buf_len,
&ind));
+ // Get minimum negative float value
+ EXPECT_EQ(-std::numeric_limits<float>::max(), float_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 20, SQL_C_DEFAULT, &float_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<float>::max(), float_val);
+
+ // Double
+ SQLDOUBLE double_val;
+ buf_len = sizeof(double_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 21, SQL_C_DEFAULT, &double_val, buf_len,
&ind));
+ // Get minimum negative double value
+ EXPECT_EQ(-std::numeric_limits<SQLDOUBLE>::max(), double_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 22, SQL_C_DEFAULT, &double_val, buf_len,
&ind));
+ EXPECT_EQ(std::numeric_limits<SQLDOUBLE>::max(), double_val);
+
+ // Bit
+ bool bit_val;
+ buf_len = sizeof(bit_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 23, SQL_C_DEFAULT, &bit_val, buf_len,
&ind));
+ EXPECT_EQ(false, bit_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 24, SQL_C_DEFAULT, &bit_val, buf_len,
&ind));
+ EXPECT_EQ(true, bit_val);
+
+ // Characters
+
+ // Char will be fetched as wchar by default
+ SQLWCHAR wchar_val[2];
+ size_t wchar_size = GetSqlWCharSize();
+ buf_len = wchar_size * 2;
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 25, SQL_C_DEFAULT, &wchar_val, buf_len,
&ind));
+ EXPECT_EQ(L'Z', wchar_val[0]);
+
+ // WChar
+ SQLWCHAR wchar_val2[2];
+ buf_len = wchar_size * 2;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 26, SQL_C_DEFAULT, &wchar_val2, buf_len,
&ind));
+ EXPECT_EQ(L'你', wchar_val2[0]);
+
+ // WVarchar
+ SQLWCHAR wvarchar_val[3];
+ buf_len = wchar_size * 3;
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 27, SQL_C_DEFAULT, &wvarchar_val, buf_len,
&ind));
+ EXPECT_EQ(L'你', wvarchar_val[0]);
+ EXPECT_EQ(L'好', wvarchar_val[1]);
+
+ // Varchar will be fetched as WVarchar by default
+ SQLWCHAR wvarchar_val2[4];
+ buf_len = wchar_size * 4;
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 28, SQL_C_DEFAULT, &wvarchar_val2, buf_len,
&ind));
+ EXPECT_EQ(L'X', wvarchar_val2[0]);
+ EXPECT_EQ(L'Y', wvarchar_val2[1]);
+ EXPECT_EQ(L'Z', wvarchar_val2[2]);
+
+ // Date and Timestamp
+
+ // Date
+ SQL_DATE_STRUCT date_var{};
+ buf_len = sizeof(date_var);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 29, SQL_C_DEFAULT, &date_var, buf_len,
&ind));
+ // Check min values for date. Min valid year is 1400.
+ EXPECT_EQ(1, date_var.day);
+ EXPECT_EQ(1, date_var.month);
+ EXPECT_EQ(1400, date_var.year);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 30, SQL_C_DEFAULT, &date_var, buf_len,
&ind));
+ // Check max values for date. Max valid year is 9999.
+ EXPECT_EQ(31, date_var.day);
+ EXPECT_EQ(12, date_var.month);
+ EXPECT_EQ(9999, date_var.year);
+
+ // Timestamp
+ SQL_TIMESTAMP_STRUCT timestamp_var{};
+ buf_len = sizeof(timestamp_var);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 31, SQL_C_DEFAULT, ×tamp_var, buf_len,
&ind));
+ // Check min values for date. Min valid year is 1400.
+ EXPECT_EQ(1, timestamp_var.day);
+ EXPECT_EQ(1, timestamp_var.month);
+ EXPECT_EQ(1400, timestamp_var.year);
+ EXPECT_EQ(0, timestamp_var.hour);
+ EXPECT_EQ(0, timestamp_var.minute);
+ EXPECT_EQ(0, timestamp_var.second);
+ EXPECT_EQ(0, timestamp_var.fraction);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 32, SQL_C_DEFAULT, ×tamp_var, buf_len,
&ind));
+ // Check max values for date. Max valid year is 9999.
+ EXPECT_EQ(31, timestamp_var.day);
+ EXPECT_EQ(12, timestamp_var.month);
+ EXPECT_EQ(9999, timestamp_var.year);
+ EXPECT_EQ(23, timestamp_var.hour);
+ EXPECT_EQ(59, timestamp_var.minute);
+ EXPECT_EQ(59, timestamp_var.second);
+ EXPECT_EQ(0, timestamp_var.fraction);
+}
+
+TEST_F(StatementRemoteTest, TestSQLExecDirectTimeQueryDefaultType) {
+ // Mock server test is skipped due to limitation on the mock server.
+ // Time type from mock server does not include the fraction
+
+ 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<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ SQL_TIME_STRUCT time_var{};
+ SQLLEN buf_len = sizeof(time_var);
+ SQLLEN ind;
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 1, SQL_C_DEFAULT, &time_var, buf_len,
&ind));
+ // Check min values for time.
+ EXPECT_EQ(0, time_var.hour);
+ EXPECT_EQ(0, time_var.minute);
+ EXPECT_EQ(0, time_var.second);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 2, SQL_C_DEFAULT, &time_var, buf_len,
&ind));
+ // Check max values for time.
+ EXPECT_EQ(23, time_var.hour);
+ EXPECT_EQ(59, time_var.minute);
+ EXPECT_EQ(59, time_var.second);
+}
+
+TEST_F(StatementRemoteTest, 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
+
+ std::wstring wsql = L"SELECT from_hex('ABCDEF') AS c_varbinary;";
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ // varbinary
+ std::vector<int8_t> varbinary_val(3);
+ SQLLEN buf_len = varbinary_val.size();
+ SQLLEN ind;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 1, SQL_C_DEFAULT, &varbinary_val[0],
buf_len, &ind));
+ EXPECT_EQ('\xAB', varbinary_val[0]);
+ EXPECT_EQ('\xCD', varbinary_val[1]);
+ EXPECT_EQ('\xEF', varbinary_val[2]);
+}
+
+TYPED_TEST(StatementTest, TestSQLExecDirectGuidQueryUnsupported) {
+ // Query GUID as string as SQLite does not support GUID
+ std::wstring wsql = L"SELECT 'C77313CF-4E08-47CE-B6DF-94DD2FCF3541' AS
guid;";
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ SQLGUID guid_var;
+ SQLLEN buf_len = sizeof(guid_var);
+ SQLLEN ind;
+ ASSERT_EQ(SQL_ERROR, SQLGetData(this->stmt, 1, SQL_C_GUID, &guid_var,
buf_len, &ind));
+ // GUID is not supported by ODBC
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY000);
+}
+
+TYPED_TEST(StatementTest, TestSQLExecDirectRowFetching) {
+ std::wstring wsql =
+ LR"(
+ SELECT 1 AS small_table
+ UNION ALL
+ SELECT 2
+ UNION ALL
+ SELECT 3;
+ )";
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ // Fetch row 1
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ SQLINTEGER val;
+ SQLLEN buf_len = sizeof(val);
+ SQLLEN ind;
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len,
&ind));
+
+ // Verify 1 is returned
+ EXPECT_EQ(1, val);
+
+ // Fetch row 2
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len,
&ind));
+
+ // Verify 2 is returned
+ EXPECT_EQ(2, val);
+
+ // Fetch row 3
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len,
&ind));
+
+ // Verify 3 is returned
+ EXPECT_EQ(3, val);
+
+ // Verify result set has no more data beyond row 3
+ ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
+
+ ASSERT_EQ(SQL_ERROR, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, &ind));
+
+ // Invalid cursor state
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState24000);
+}
+
+TYPED_TEST(StatementTest, TestSQLExecDirectVarcharTruncation) {
+ std::wstring wsql = L"SELECT 'VERY LONG STRING here' AS string_col;";
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ const int len = 17;
+ SQLCHAR char_val[len];
+ SQLLEN buf_len = sizeof(SQLCHAR) * len;
+ SQLLEN ind;
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val, buf_len, &ind));
+ // Verify string truncation is reported
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState01004);
+
+ EXPECT_EQ(std::string("VERY LONG STRING"),
ODBC::SqlStringToString(char_val));
+ EXPECT_EQ(21, ind);
+
+ // Fetch same column 2nd time
+ const int len2 = 2;
+ SQLCHAR char_val2[len2];
+ buf_len = sizeof(SQLCHAR) * len2;
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val2, buf_len, &ind));
+ // Verify string truncation is reported
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState01004);
+
+ EXPECT_EQ(std::string(" "), ODBC::SqlStringToString(char_val2));
+ EXPECT_EQ(5, ind);
+
+ // Fetch same column 3rd time
+ const int len3 = 5;
+ SQLCHAR char_val3[len3];
+ buf_len = sizeof(SQLCHAR) * len3;
+
+ // Verify that there is no more truncation reports. The full string has been
fetched.
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val3, buf_len, &ind));
+
+ EXPECT_EQ(std::string("here"), ODBC::SqlStringToString(char_val3));
+ EXPECT_EQ(4, ind);
+
+ // Attempt to fetch data 4th time
+ SQLCHAR char_val4[len];
+ // Verify SQL_NO_DATA is returned
+ ASSERT_EQ(SQL_NO_DATA, SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val4, 0,
&ind));
+}
+
+TYPED_TEST(StatementTest, TestSQLExecDirectWVarcharTruncation) {
+ std::wstring wsql = L"SELECT 'VERY LONG Unicode STRING 句子 here' AS
wstring_col;";
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ const int len = 28;
+ SQLWCHAR wchar_val[len];
+ size_t wchar_size = GetSqlWCharSize();
+ SQLLEN buf_len = wchar_size * len;
+ SQLLEN ind;
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val, buf_len, &ind));
+ // Verify string truncation is reported
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState01004);
+
+ EXPECT_EQ(std::wstring(L"VERY LONG Unicode STRING 句子"),
std::wstring(wchar_val));
+ EXPECT_EQ(32 * wchar_size, ind);
+
+ // Fetch same column 2nd time
+ const int len2 = 2;
+ SQLWCHAR wchar_val2[len2];
+ buf_len = wchar_size * len2;
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val2, buf_len,
&ind));
+ // Verify string truncation is reported
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState01004);
+
+ EXPECT_EQ(std::wstring(L" "), std::wstring(wchar_val2));
+ EXPECT_EQ(5 * wchar_size, ind);
+
+ // Fetch same column 3rd time
+ const int len3 = 5;
+ SQLWCHAR wchar_val3[len3];
+ buf_len = wchar_size * len3;
+
+ // Verify that there is no more truncation reports. The full string has been
fetched.
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val3, buf_len,
&ind));
+
+ EXPECT_EQ(std::wstring(L"here"), std::wstring(wchar_val3));
+ EXPECT_EQ(4 * wchar_size, ind);
+
+ // Attempt to fetch data 4th time
+ SQLWCHAR wchar_val4[len];
+ // Verify SQL_NO_DATA is returned
+ ASSERT_EQ(SQL_NO_DATA, SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val4,
0, &ind));
+}
+
+TEST_F(StatementMockTest, TestSQLExecDirectVarbinaryTruncation) {
+ // Have binary test on mock test base as remote test servers tend to have
different
+ // formats for binary data
+
+ std::wstring wsql = L"SELECT X'ABCDEFAB' AS c_varbinary;";
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ // varbinary
+ std::vector<int8_t> varbinary_val(3);
+ SQLLEN buf_len = varbinary_val.size();
+ SQLLEN ind;
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val[0],
buf_len, &ind));
+ // Verify binary truncation is reported
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState01004);
+ EXPECT_EQ('\xAB', varbinary_val[0]);
+ EXPECT_EQ('\xCD', varbinary_val[1]);
+ EXPECT_EQ('\xEF', varbinary_val[2]);
+ EXPECT_EQ(4, ind);
+
+ // Fetch same column 2nd time
+ std::vector<int8_t> varbinary_val2(1);
+ buf_len = varbinary_val2.size();
+
+ // Verify that there is no more truncation reports. The full binary has been
fetched.
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val2[0],
buf_len, &ind));
+
+ EXPECT_EQ('\xAB', varbinary_val[0]);
+ EXPECT_EQ(1, ind);
+
+ // Attempt to fetch data 3rd time
+ std::vector<int8_t> varbinary_val3(1);
+ buf_len = varbinary_val3.size();
+ // Verify SQL_NO_DATA is returned
+ ASSERT_EQ(SQL_NO_DATA,
+ SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val3[0],
buf_len, &ind));
+}
+
+TYPED_TEST(StatementTest, DISABLED_TestSQLExecDirectFloatTruncation) {
+ // Test is disabled until float truncation is supported.
+ // GH-46985: return warning message instead of error on float truncation case
+ std::wstring wsql;
+ if constexpr (std::is_same_v<TypeParam, StatementMockTest>) {
+ wsql = std::wstring(L"SELECT CAST(1.234 AS REAL) AS float_val");
+ } else if constexpr (std::is_same_v<TypeParam, StatementRemoteTest>) {
+ wsql = std::wstring(L"SELECT CAST(1.234 AS FLOAT) AS float_val");
+ }
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ int16_t ssmall_int_val;
+
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLGetData(this->stmt, 1, SQL_C_SSHORT, &ssmall_int_val, 0, 0));
+ // Verify float truncation is reported
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState01S07);
+
+ EXPECT_EQ(1, ssmall_int_val);
+}
+
+TEST_F(StatementRemoteTest, TestSQLExecDirectNullQuery) {
+ // Limitation on mock test server prevents null from working properly, so
use remote
+ // server instead. Mock server has type `DENSE_UNION` for null column data.
+
+ std::wstring wsql = L"SELECT null as null_col;";
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ SQLINTEGER val;
+ SQLLEN ind;
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, &ind));
+
+ // Verify SQL_NULL_DATA is returned for indicator
+ EXPECT_EQ(SQL_NULL_DATA, ind);
+}
+
+TEST_F(StatementMockTest, 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
+
+ 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<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ SQLINTEGER val;
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0));
+ // Verify 1 is returned for non-truncation case.
+ EXPECT_EQ(1, val);
+
+ // Char
+ const int len = 17;
+ SQLCHAR char_val[len];
+ SQLLEN buf_len = sizeof(SQLCHAR) * len;
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLGetData(this->stmt, 2, SQL_C_CHAR, &char_val, buf_len, 0));
+ // Verify string truncation is reported
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState01004);
+
+ // WChar
+ const int len2 = 28;
+ SQLWCHAR wchar_val[len2];
+ size_t wchar_size = GetSqlWCharSize();
+ buf_len = wchar_size * len2;
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLGetData(this->stmt, 3, SQL_C_WCHAR, &wchar_val, buf_len, 0));
+ // Verify string truncation is reported
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState01004);
+
+ // varbinary
+ std::vector<int8_t> varbinary_val(3);
+ buf_len = varbinary_val.size();
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLGetData(this->stmt, 4, SQL_C_BINARY, &varbinary_val[0],
buf_len, 0));
+ // Verify binary truncation is reported
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState01004);
+}
+
+TEST_F(StatementRemoteTest, TestSQLExecDirectNullQueryNullIndicator) {
+ // Limitation on mock test server prevents null from working properly, so
use remote
+ // server instead. Mock server has type `DENSE_UNION` for null column data.
+
+ std::wstring wsql = L"SELECT null as null_col;";
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ SQLINTEGER val;
+
+ ASSERT_EQ(SQL_ERROR, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0));
+ // Verify invalid null indicator is reported, as it is required
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState22002);
+}
+
+TYPED_TEST(StatementTest, TestSQLExecDirectIgnoreInvalidBufLen) {
+ // Verify the driver ignores invalid buffer length for fixed data types
+
+ std::wstring wsql = this->GetQueryAllDataTypes();
+ std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLExecDirect(this->stmt, &sql0[0],
static_cast<SQLINTEGER>(sql0.size())));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+ // Numeric Types
+
+ // Signed Tiny Int
+ int8_t stiny_int_val;
+ SQLLEN invalid_buf_len = -1;
+ SQLLEN ind;
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 1, SQL_C_STINYINT,
&stiny_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<int8_t>::min(), stiny_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 2, SQL_C_STINYINT,
&stiny_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<int8_t>::max(), stiny_int_val);
+
+ // Unsigned Tiny Int
+ uint8_t utiny_int_val;
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 3, SQL_C_UTINYINT,
&utiny_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<uint8_t>::min(), utiny_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 4, SQL_C_UTINYINT,
&utiny_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<uint8_t>::max(), utiny_int_val);
+
+ // Signed Small Int
+ int16_t ssmall_int_val;
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 5, SQL_C_SSHORT,
&ssmall_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<int16_t>::min(), ssmall_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 6, SQL_C_SSHORT,
&ssmall_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<int16_t>::max(), ssmall_int_val);
+
+ // Unsigned Small Int
+ uint16_t usmall_int_val;
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 7, SQL_C_USHORT,
&usmall_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<uint16_t>::min(), usmall_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 8, SQL_C_USHORT,
&usmall_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(), usmall_int_val);
+
+ // Signed Integer
+ SQLINTEGER slong_val;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 9, SQL_C_SLONG, &slong_val,
invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLINTEGER>::min(), slong_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 10, SQL_C_SLONG, &slong_val,
invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLINTEGER>::max(), slong_val);
+
+ // Unsigned Integer
+ SQLUINTEGER ulong_val;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 11, SQL_C_ULONG, &ulong_val,
invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLUINTEGER>::min(), ulong_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 12, SQL_C_ULONG, &ulong_val,
invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLUINTEGER>::max(), ulong_val);
+
+ // Signed Big Int
+ SQLBIGINT sbig_int_val;
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 13, SQL_C_SBIGINT,
&sbig_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLBIGINT>::min(), sbig_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 14, SQL_C_SBIGINT,
&sbig_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLBIGINT>::max(), sbig_int_val);
+
+ // Unsigned Big Int
+ SQLUBIGINT ubig_int_val;
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 15, SQL_C_UBIGINT,
&ubig_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLUBIGINT>::min(), ubig_int_val);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 16, SQL_C_UBIGINT,
&ubig_int_val,
+ invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLUBIGINT>::max(), ubig_int_val);
+
+ // Decimal
+ SQL_NUMERIC_STRUCT decimal_val;
+ memset(&decimal_val, 0, sizeof(decimal_val));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 17, SQL_C_NUMERIC,
&decimal_val,
+ invalid_buf_len, &ind));
+ // Check for negative decimal_val value
+ EXPECT_EQ(0, decimal_val.sign);
+ EXPECT_EQ(0, decimal_val.scale);
+ EXPECT_EQ(38, decimal_val.precision);
+ 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));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 18, SQL_C_NUMERIC,
&decimal_val,
+ invalid_buf_len, &ind));
+ // Check for positive decimal_val value
+ EXPECT_EQ(1, decimal_val.sign);
+ EXPECT_EQ(0, decimal_val.scale);
+ EXPECT_EQ(38, decimal_val.precision);
+ 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;
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 19, SQL_C_FLOAT, &float_val,
invalid_buf_len, &ind));
+ // Get minimum negative float value
+ EXPECT_EQ(-std::numeric_limits<float>::max(), float_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 20, SQL_C_FLOAT, &float_val,
invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<float>::max(), float_val);
+
+ // Double
+ SQLDOUBLE double_val;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 21, SQL_C_DOUBLE, &double_val,
invalid_buf_len, &ind));
+ // Get minimum negative double value
+ EXPECT_EQ(-std::numeric_limits<SQLDOUBLE>::max(), double_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 22, SQL_C_DOUBLE, &double_val,
invalid_buf_len, &ind));
+ EXPECT_EQ(std::numeric_limits<SQLDOUBLE>::max(), double_val);
+
+ // Bit
+ bool bit_val;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 23, SQL_C_BIT, &bit_val, invalid_buf_len,
&ind));
+ EXPECT_EQ(false, bit_val);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetData(this->stmt, 24, SQL_C_BIT, &bit_val, invalid_buf_len,
&ind));
+ EXPECT_EQ(true, bit_val);
+
+ // Date and Timestamp
+
+ // Date
+ SQL_DATE_STRUCT date_var{};
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 29, SQL_C_TYPE_DATE, &date_var,
+ invalid_buf_len, &ind));
+ // Check min values for date. Min valid year is 1400.
+ EXPECT_EQ(1, date_var.day);
+ EXPECT_EQ(1, date_var.month);
+ EXPECT_EQ(1400, date_var.year);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 30, SQL_C_TYPE_DATE, &date_var,
+ invalid_buf_len, &ind));
+ // Check max values for date. Max valid year is 9999.
+ EXPECT_EQ(31, date_var.day);
+ EXPECT_EQ(12, date_var.month);
+ EXPECT_EQ(9999, date_var.year);
+
+ // Timestamp
+ SQL_TIMESTAMP_STRUCT timestamp_var{};
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 31, SQL_C_TYPE_TIMESTAMP,
×tamp_var,
+ invalid_buf_len, &ind));
+ // Check min values for date. Min valid year is 1400.
+ EXPECT_EQ(1, timestamp_var.day);
+ EXPECT_EQ(1, timestamp_var.month);
+ EXPECT_EQ(1400, timestamp_var.year);
+ EXPECT_EQ(0, timestamp_var.hour);
+ EXPECT_EQ(0, timestamp_var.minute);
+ EXPECT_EQ(0, timestamp_var.second);
+ EXPECT_EQ(0, timestamp_var.fraction);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 32, SQL_C_TYPE_TIMESTAMP,
×tamp_var,
+ invalid_buf_len, &ind));
+ // Check max values for date. Max valid year is 9999.
+ EXPECT_EQ(31, timestamp_var.day);
+ EXPECT_EQ(12, timestamp_var.month);
+ EXPECT_EQ(9999, timestamp_var.year);
+ EXPECT_EQ(23, timestamp_var.hour);
+ EXPECT_EQ(59, timestamp_var.minute);
+ EXPECT_EQ(59, timestamp_var.second);
+ EXPECT_EQ(0, timestamp_var.fraction);
+}
+
TYPED_TEST(StatementTest, TestSQLNativeSqlReturnsInputString) {
SQLWCHAR buf[1024];
SQLINTEGER buf_char_len = sizeof(buf) / GetSqlWCharSize();