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, 
&timestamp_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, 
&timestamp_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, &timestamp_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, &timestamp_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, 
&timestamp_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, 
&timestamp_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();

Reply via email to