This is an automated email from the ASF dual-hosted git repository.

jduong 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 d25f387fdb GH-47720: [C++][FlightRPC] ODBC Columns Metadata (#48049)
d25f387fdb is described below

commit d25f387fdb53895d5143deeabde14bf757c40a77
Author: Alina (Xi) Li <[email protected]>
AuthorDate: Mon Dec 15 02:44:40 2025 -0800

    GH-47720: [C++][FlightRPC] ODBC Columns Metadata (#48049)
    
    ### Rationale for this change
    Add support for returning columns metadata from the connected data source.
    
    ### What changes are included in this PR?
    - SQLColumns & Test
    ### Are these changes tested?
    Tested in local MSVC
    
    ### Are there any user-facing changes?
    N/A
    * GitHub Issue: #47720
    
    Authored-by: Alina (Xi) Li <[email protected]>
    Signed-off-by: James Duong <[email protected]>
---
 cpp/src/arrow/flight/sql/odbc/odbc_api.cc          |  30 +-
 cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt |   1 +
 .../arrow/flight/sql/odbc/tests/columns_test.cc    | 963 +++++++++++++++++++++
 .../arrow/flight/sql/odbc/tests/odbc_test_suite.cc |  13 +-
 4 files changed, 994 insertions(+), 13 deletions(-)

diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index 8c4609ec9d..464f712a5d 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -41,11 +41,7 @@ SQLRETURN SQLAllocHandle(SQLSMALLINT type, SQLHANDLE parent, 
SQLHANDLE* result)
   ARROW_LOG(DEBUG) << "SQLAllocHandle called with type: " << type
                    << ", parent: " << parent
                    << ", result: " << static_cast<const void*>(result);
-  // GH-47706 TODO: Add tests for SQLAllocStmt, pre-requisite requires
-  // SQLDriverConnect implementation
-
   *result = nullptr;
-
   switch (type) {
     case SQL_HANDLE_ENV: {
       using ODBC::ODBCEnvironment;
@@ -141,9 +137,6 @@ SQLRETURN SQLAllocHandle(SQLSMALLINT type, SQLHANDLE 
parent, SQLHANDLE* result)
 SQLRETURN SQLFreeHandle(SQLSMALLINT type, SQLHANDLE handle) {
   ARROW_LOG(DEBUG) << "SQLFreeHandle called with type: " << type
                    << ", handle: " << handle;
-  // GH-47706 TODO: Add tests for SQLFreeStmt, pre-requisite requires
-  // SQLAllocStmt tests
-
   switch (type) {
     case SQL_HANDLE_ENV: {
       using ODBC::ODBCEnvironment;
@@ -234,7 +227,6 @@ SQLRETURN SQLFreeStmt(SQLHSTMT handle, SQLUSMALLINT option) 
{
     }
 
     case SQL_UNBIND: {
-      // GH-47716 TODO: Add tests for SQLBindCol unbinding
       using ODBC::ODBCDescriptor;
       using ODBC::ODBCStatement;
       return ODBCStatement::ExecuteWithDiagnostics(handle, SQL_ERROR, [=]() {
@@ -280,7 +272,6 @@ SQLRETURN SQLGetDiagField(SQLSMALLINT handle_type, 
SQLHANDLE handle,
                    << ", diag_info_ptr: " << diag_info_ptr
                    << ", buffer_length: " << buffer_length << ", 
string_length_ptr: "
                    << static_cast<const void*>(string_length_ptr);
-  // GH-46575 TODO: Add tests for SQLGetDiagField
   using ODBC::GetStringAttribute;
   using ODBC::ODBCConnection;
   using ODBC::ODBCDescriptor;
@@ -545,7 +536,6 @@ SQLRETURN SQLGetDiagRec(SQLSMALLINT handle_type, SQLHANDLE 
handle, SQLSMALLINT r
                    << ", message_text: " << static_cast<const 
void*>(message_text)
                    << ", buffer_length: " << buffer_length
                    << ", text_length_ptr: " << static_cast<const 
void*>(text_length_ptr);
-  // GH-46575 TODO: Add tests for SQLGetDiagRec
   using arrow::flight::sql::odbc::Diagnostics;
   using ODBC::GetStringAttribute;
   using ODBC::ODBCConnection;
@@ -1227,8 +1217,24 @@ SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* 
catalog_name,
                    << ", table_name_length: " << table_name_length
                    << ", column_name: " << static_cast<const 
void*>(column_name)
                    << ", column_name_length: " << column_name_length;
-  // GH-47720 TODO: Implement SQLColumns
-  return SQL_INVALID_HANDLE;
+
+  using ODBC::ODBCStatement;
+  using ODBC::SqlWcharToString;
+
+  return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
+    ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
+
+    std::string catalog = SqlWcharToString(catalog_name, catalog_name_length);
+    std::string schema = SqlWcharToString(schema_name, schema_name_length);
+    std::string table = SqlWcharToString(table_name, table_name_length);
+    std::string column = SqlWcharToString(column_name, column_name_length);
+
+    statement->GetColumns(catalog_name ? &catalog : nullptr,
+                          schema_name ? &schema : nullptr, table_name ? &table 
: nullptr,
+                          column_name ? &column : nullptr);
+
+    return SQL_SUCCESS;
+  });
 }
 
 SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT record_number,
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt 
b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
index e2f83dcdbf..2f4ed9964d 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
+++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
@@ -34,6 +34,7 @@ add_arrow_test(flight_sql_odbc_test
                SOURCES
                odbc_test_suite.cc
                odbc_test_suite.h
+               columns_test.cc
                connection_attr_test.cc
                connection_info_test.cc
                connection_test.cc
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc 
b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc
new file mode 100644
index 0000000000..81e97a0928
--- /dev/null
+++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc
@@ -0,0 +1,963 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+#include "arrow/flight/sql/odbc/tests/odbc_test_suite.h"
+
+#include "arrow/flight/sql/odbc/odbc_impl/platform.h"
+
+#include <sql.h>
+#include <sqltypes.h>
+#include <sqlucode.h>
+
+#include <gtest/gtest.h>
+
+namespace arrow::flight::sql::odbc {
+
+template <typename T>
+class ColumnsTest : public T {};
+
+class ColumnsMockTest : public FlightSQLODBCMockTestBase {};
+class ColumnsRemoteTest : public FlightSQLODBCRemoteTestBase {};
+using TestTypes = ::testing::Types<ColumnsMockTest, ColumnsRemoteTest>;
+TYPED_TEST_SUITE(ColumnsTest, TestTypes);
+
+template <typename T>
+class ColumnsOdbcV2Test : public T {};
+
+class ColumnsOdbcV2MockTest : public FlightSQLOdbcV2MockTestBase {};
+class ColumnsOdbcV2RemoteTest : public FlightSQLOdbcV2RemoteTestBase {};
+using TestTypesOdbcV2 = ::testing::Types<ColumnsOdbcV2MockTest, 
ColumnsOdbcV2RemoteTest>;
+TYPED_TEST_SUITE(ColumnsOdbcV2Test, TestTypesOdbcV2);
+
+namespace {
+// Helper functions
+void CheckSQLColumns(
+    SQLHSTMT stmt, const std::wstring& expected_table,
+    const std::wstring& expected_column, const SQLINTEGER& expected_data_type,
+    const std::wstring& expected_type_name, const SQLINTEGER& 
expected_column_size,
+    const SQLINTEGER& expected_buffer_length, const SQLSMALLINT& 
expected_decimal_digits,
+    const SQLSMALLINT& expected_num_prec_radix, const SQLSMALLINT& 
expected_nullable,
+    const SQLSMALLINT& expected_sql_data_type, const SQLSMALLINT& 
expected_date_time_sub,
+    const SQLINTEGER& expected_octet_char_length,
+    const SQLINTEGER& expected_ordinal_position,
+    const std::wstring& expected_is_nullable) {
+  CheckStringColumnW(stmt, 3, expected_table);   // table name
+  CheckStringColumnW(stmt, 4, expected_column);  // column name
+
+  CheckIntColumn(stmt, 5, expected_data_type);  // data type
+
+  CheckStringColumnW(stmt, 6, expected_type_name);  // type name
+
+  CheckIntColumn(stmt, 7, expected_column_size);    // column size
+  CheckIntColumn(stmt, 8, expected_buffer_length);  // buffer length
+
+  CheckSmallIntColumn(stmt, 9, expected_decimal_digits);   // decimal digits
+  CheckSmallIntColumn(stmt, 10, expected_num_prec_radix);  // num prec radix
+  CheckSmallIntColumn(stmt, 11,
+                      expected_nullable);  // nullable
+
+  CheckNullColumnW(stmt, 12);  // remarks
+  CheckNullColumnW(stmt, 13);  // column def
+
+  CheckSmallIntColumn(stmt, 14, expected_sql_data_type);  // sql data type
+  CheckSmallIntColumn(stmt, 15, expected_date_time_sub);  // sql date type sub
+  CheckIntColumn(stmt, 16, expected_octet_char_length);   // char octet length
+  CheckIntColumn(stmt, 17,
+                 expected_ordinal_position);  // oridinal position
+
+  CheckStringColumnW(stmt, 18, expected_is_nullable);  // is nullable
+}
+
+void CheckMockSQLColumns(
+    SQLHSTMT stmt, const std::wstring& expected_catalog,
+    const std::wstring& expected_table, const std::wstring& expected_column,
+    const SQLINTEGER& expected_data_type, const std::wstring& 
expected_type_name,
+    const SQLINTEGER& expected_column_size, const SQLINTEGER& 
expected_buffer_length,
+    const SQLSMALLINT& expected_decimal_digits,
+    const SQLSMALLINT& expected_num_prec_radix, const SQLSMALLINT& 
expected_nullable,
+    const SQLSMALLINT& expected_sql_data_type, const SQLSMALLINT& 
expected_date_time_sub,
+    const SQLINTEGER& expected_octet_char_length,
+    const SQLINTEGER& expected_ordinal_position,
+    const std::wstring& expected_is_nullable) {
+  CheckStringColumnW(stmt, 1, expected_catalog);  // catalog
+  CheckNullColumnW(stmt, 2);                      // schema
+
+  CheckSQLColumns(stmt, expected_table, expected_column, expected_data_type,
+                  expected_type_name, expected_column_size, 
expected_buffer_length,
+                  expected_decimal_digits, expected_num_prec_radix, 
expected_nullable,
+                  expected_sql_data_type, expected_date_time_sub,
+                  expected_octet_char_length, expected_ordinal_position,
+                  expected_is_nullable);
+}
+
+void CheckRemoteSQLColumns(
+    SQLHSTMT stmt, const std::wstring& expected_schema,
+    const std::wstring& expected_table, const std::wstring& expected_column,
+    const SQLINTEGER& expected_data_type, const std::wstring& 
expected_type_name,
+    const SQLINTEGER& expected_column_size, const SQLINTEGER& 
expected_buffer_length,
+    const SQLSMALLINT& expected_decimal_digits,
+    const SQLSMALLINT& expected_num_prec_radix, const SQLSMALLINT& 
expected_nullable,
+    const SQLSMALLINT& expected_sql_data_type, const SQLSMALLINT& 
expected_date_time_sub,
+    const SQLINTEGER& expected_octet_char_length,
+    const SQLINTEGER& expected_ordinal_position,
+    const std::wstring& expected_is_nullable) {
+  CheckNullColumnW(stmt, 1);                     // catalog
+  CheckStringColumnW(stmt, 2, expected_schema);  // schema
+  CheckSQLColumns(stmt, expected_table, expected_column, expected_data_type,
+                  expected_type_name, expected_column_size, 
expected_buffer_length,
+                  expected_decimal_digits, expected_num_prec_radix, 
expected_nullable,
+                  expected_sql_data_type, expected_date_time_sub,
+                  expected_octet_char_length, expected_ordinal_position,
+                  expected_is_nullable);
+}
+
+}  // namespace
+
+TYPED_TEST(ColumnsTest, SQLColumnsTestInputData) {
+  SQLWCHAR catalog_name[] = L"";
+  SQLWCHAR schema_name[] = L"";
+  SQLWCHAR table_name[] = L"";
+  SQLWCHAR column_name[] = L"";
+
+  // All values populated
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColumns(this->stmt, catalog_name, sizeof(catalog_name), 
schema_name,
+                       sizeof(schema_name), table_name, sizeof(table_name), 
column_name,
+                       sizeof(column_name)));
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+
+  // Sizes are zeros
+  EXPECT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, catalog_name, 0, schema_name, 
0,
+                                    table_name, 0, column_name, 0));
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+
+  // Names are nulls
+  EXPECT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, nullptr, sizeof(catalog_name), 
nullptr,
+                                    sizeof(schema_name), nullptr, 
sizeof(table_name),
+                                    nullptr, sizeof(column_name)));
+  ValidateFetch(this->stmt, SQL_SUCCESS);
+  // Close statement cursor to avoid leaving in an invalid state
+  SQLFreeStmt(this->stmt, SQL_CLOSE);
+
+  // Names are nulls and sizes are zeros
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColumns(this->stmt, nullptr, 0, nullptr, 0, nullptr, 0, 
nullptr, 0));
+  ValidateFetch(this->stmt, SQL_SUCCESS);
+}
+
+TEST_F(ColumnsMockTest, TestSQLColumnsAllColumns) {
+  // Check table pattern and column pattern returns all columns
+
+  // Attempt to get all columns
+  SQLWCHAR table_pattern[] = L"%";
+  SQLWCHAR column_pattern[] = L"%";
+
+  ASSERT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                    table_pattern, SQL_NTS, column_pattern, 
SQL_NTS));
+
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  // mock limitation: SQLite mock server returns 10 for bigint size when spec 
indicates
+  // should be 19
+  // DECIMAL_DIGITS should be 0 for bigint type since it is exact
+  // mock limitation: SQLite mock server returns 10 for bigint decimal digits 
when spec
+  // indicates should be 0
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),          // expected_catalog
+                      std::wstring(L"foreignTable"),  // expected_table
+                      std::wstring(L"id"),            // expected_column
+                      SQL_BIGINT,                     // expected_data_type
+                      std::wstring(L"BIGINT"),        // expected_type_name
+                      10,  // expected_column_size (mock returns 10 instead of 
19)
+                      8,   // expected_buffer_length
+                      15,  // expected_decimal_digits (mock returns 15 instead 
of 0)
+                      10,  // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BIGINT,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      8,                      // expected_octet_char_length
+                      1,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 2nd Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),          // expected_catalog
+                      std::wstring(L"foreignTable"),  // expected_table
+                      std::wstring(L"foreignName"),   // expected_column
+                      SQL_WVARCHAR,                   // expected_data_type
+                      std::wstring(L"WVARCHAR"),      // expected_type_name
+                      0,   // expected_column_size (mock server limitation: 
returns 0 for
+                           // varchar(100), the ODBC spec expects 100)
+                      0,   // expected_buffer_length
+                      15,  // expected_decimal_digits
+                      0,   // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_WVARCHAR,           // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      0,                      // expected_octet_char_length
+                      2,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 3rd Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),          // expected_catalog
+                      std::wstring(L"foreignTable"),  // expected_table
+                      std::wstring(L"value"),         // expected_column
+                      SQL_BIGINT,                     // expected_data_type
+                      std::wstring(L"BIGINT"),        // expected_type_name
+                      10,  // expected_column_size (mock returns 10 instead of 
19)
+                      8,   // expected_buffer_length
+                      15,  // expected_decimal_digits (mock returns 15 instead 
of 0)
+                      10,  // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BIGINT,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      8,                      // expected_octet_char_length
+                      3,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 4th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),      // expected_catalog
+                      std::wstring(L"intTable"),  // expected_table
+                      std::wstring(L"id"),        // expected_column
+                      SQL_BIGINT,                 // expected_data_type
+                      std::wstring(L"BIGINT"),    // expected_type_name
+                      10,  // expected_column_size (mock returns 10 instead of 
19)
+                      8,   // expected_buffer_length
+                      15,  // expected_decimal_digits (mock returns 15 instead 
of 0)
+                      10,  // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BIGINT,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      8,                      // expected_octet_char_length
+                      1,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 5th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),      // expected_catalog
+                      std::wstring(L"intTable"),  // expected_table
+                      std::wstring(L"keyName"),   // expected_column
+                      SQL_WVARCHAR,               // expected_data_type
+                      std::wstring(L"WVARCHAR"),  // expected_type_name
+                      0,   // expected_column_size (mock server limitation: 
returns 0 for
+                           // varchar(100), the ODBC spec expects 100)
+                      0,   // expected_buffer_length
+                      15,  // expected_decimal_digits
+                      0,   // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_WVARCHAR,           // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      0,                      // expected_octet_char_length
+                      2,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 6th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),      // expected_catalog
+                      std::wstring(L"intTable"),  // expected_table
+                      std::wstring(L"value"),     // expected_column
+                      SQL_BIGINT,                 // expected_data_type
+                      std::wstring(L"BIGINT"),    // expected_type_name
+                      10,  // expected_column_size (mock returns 10 instead of 
19)
+                      8,   // expected_buffer_length
+                      15,  // expected_decimal_digits (mock returns 15 instead 
of 0)
+                      10,  // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BIGINT,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      8,                      // expected_octet_char_length
+                      3,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 7th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),       // expected_catalog
+                      std::wstring(L"intTable"),   // expected_table
+                      std::wstring(L"foreignId"),  // expected_column
+                      SQL_BIGINT,                  // expected_data_type
+                      std::wstring(L"BIGINT"),     // expected_type_name
+                      10,  // expected_column_size (mock returns 10 instead of 
19)
+                      8,   // expected_buffer_length
+                      15,  // expected_decimal_digits (mock returns 15 instead 
of 0)
+                      10,  // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BIGINT,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      8,                      // expected_octet_char_length
+                      4,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+}
+
+TEST_F(ColumnsMockTest, TestSQLColumnsAllTypes) {
+  // Limitation: Mock server returns incorrect values for column size for some 
columns.
+  // For character and binary type columns, the driver calculates buffer 
length and char
+  // octet length from column size.
+
+  // Checks filtering table with table name pattern
+  this->CreateTableAllDataType();
+
+  // Attempt to get all columns from AllTypesTable
+  SQLWCHAR table_pattern[] = L"AllTypesTable";
+  SQLWCHAR column_pattern[] = L"%";
+
+  ASSERT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                    table_pattern, SQL_NTS, column_pattern, 
SQL_NTS));
+
+  // Fetch SQLColumn data for 1st column in AllTypesTable
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),           // expected_catalog
+                      std::wstring(L"AllTypesTable"),  // expected_table
+                      std::wstring(L"bigint_col"),     // expected_column
+                      SQL_BIGINT,                      // expected_data_type
+                      std::wstring(L"BIGINT"),         // expected_type_name
+                      10,  // expected_column_size (mock server limitation: 
returns 10,
+                           // the ODBC spec expects 19)
+                      8,   // expected_buffer_length
+                      15,  // expected_decimal_digits (mock server limitation: 
returns 15,
+                           // the ODBC spec expects 0)
+                      10,  // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BIGINT,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      8,                      // expected_octet_char_length
+                      1,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check SQLColumn data for 2nd column in AllTypesTable
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),           // expected_catalog
+                      std::wstring(L"AllTypesTable"),  // expected_table
+                      std::wstring(L"char_col"),       // expected_column
+                      SQL_WVARCHAR,                    // expected_data_type
+                      std::wstring(L"WVARCHAR"),       // expected_type_name
+                      0,   // expected_column_size (mock server limitation: 
returns 0 for
+                           // varchar(100), the ODBC spec expects 100)
+                      0,   // expected_buffer_length
+                      15,  // expected_decimal_digits
+                      0,   // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_WVARCHAR,           // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      0,                      // expected_octet_char_length
+                      2,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check SQLColumn data for 3rd column in AllTypesTable
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),           // expected_catalog
+                      std::wstring(L"AllTypesTable"),  // expected_table
+                      std::wstring(L"varbinary_col"),  // expected_column
+                      SQL_BINARY,                      // expected_data_type
+                      std::wstring(L"BINARY"),         // expected_type_name
+                      0,   // expected_column_size (mock server limitation: 
returns 0 for
+                           // BLOB column, spec expects binary data limit)
+                      0,   // expected_buffer_length
+                      15,  // expected_decimal_digits
+                      0,   // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BINARY,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      0,                      // expected_octet_char_length
+                      3,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check SQLColumn data for 4th column in AllTypesTable
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),           // expected_catalog
+                      std::wstring(L"AllTypesTable"),  // expected_table
+                      std::wstring(L"double_col"),     // expected_column
+                      SQL_DOUBLE,                      // expected_data_type
+                      std::wstring(L"DOUBLE"),         // expected_type_name
+                      15,                              // expected_column_size
+                      8,                               // 
expected_buffer_length
+                      15,                              // 
expected_decimal_digits
+                      2,                               // 
expected_num_prec_radix
+                      SQL_NULLABLE,                    // expected_nullable
+                      SQL_DOUBLE,                      // 
expected_sql_data_type
+                      NULL,                            // 
expected_date_time_sub
+                      8,                               // 
expected_octet_char_length
+                      4,                               // 
expected_ordinal_position
+                      std::wstring(L"YES"));           // expected_is_nullable
+
+  // There should be no more column data
+  ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
+}
+
+TEST_F(ColumnsMockTest, TestSQLColumnsUnicode) {
+  // Limitation: Mock server returns incorrect values for column size for some 
columns.
+  // For character and binary type columns, the driver calculates buffer 
length and char
+  // octet length from column size.
+  this->CreateUnicodeTable();
+
+  // Attempt to get all columns
+  SQLWCHAR table_pattern[] = L"数据";
+  SQLWCHAR column_pattern[] = L"%";
+
+  ASSERT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                    table_pattern, SQL_NTS, column_pattern, 
SQL_NTS));
+
+  // Check SQLColumn data for 1st column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),      // expected_catalog
+                      std::wstring(L"数据"),      // expected_table
+                      std::wstring(L"资料"),      // expected_column
+                      SQL_WVARCHAR,               // expected_data_type
+                      std::wstring(L"WVARCHAR"),  // expected_type_name
+                      0,   // expected_column_size (mock server limitation: 
returns 0 for
+                           // varchar(100), spec expects 100)
+                      0,   // expected_buffer_length
+                      15,  // expected_decimal_digits
+                      0,   // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_WVARCHAR,           // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      0,                      // expected_octet_char_length
+                      1,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // There should be no more column data
+  EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
+}
+
+TEST_F(ColumnsRemoteTest, TestSQLColumnsAllTypes) {
+  // GH-47159 TODO: Return NUM_PREC_RADIX based on whether COLUMN_SIZE 
contains number of
+  // digits or bits
+
+  SQLWCHAR table_pattern[] = L"ODBCTest";
+  SQLWCHAR column_pattern[] = L"%";
+
+  ASSERT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                    table_pattern, SQL_NTS, column_pattern, 
SQL_NTS));
+
+  // Check 1st Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),      // expected_schema
+      std::wstring(L"ODBCTest"),      // expected_table
+      std::wstring(L"sinteger_max"),  // expected_column
+      SQL_INTEGER,                    // expected_data_type
+      std::wstring(L"INTEGER"),       // expected_type_name
+      32,            // expected_column_size (remote server returns number of 
bits)
+      4,             // expected_buffer_length
+      0,             // expected_decimal_digits
+      10,            // expected_num_prec_radix
+      SQL_NULLABLE,  // expected_nullable
+      SQL_INTEGER,   // expected_sql_data_type
+      NULL,          // expected_date_time_sub
+      4,             // expected_octet_char_length
+      1,             // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 2nd Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),     // expected_schema
+      std::wstring(L"ODBCTest"),     // expected_table
+      std::wstring(L"sbigint_max"),  // expected_column
+      SQL_BIGINT,                    // expected_data_type
+      std::wstring(L"BIGINT"),       // expected_type_name
+      64,            // expected_column_size (remote server returns number of 
bits)
+      8,             // expected_buffer_length
+      0,             // expected_decimal_digits
+      10,            // expected_num_prec_radix
+      SQL_NULLABLE,  // expected_nullable
+      SQL_BIGINT,    // expected_sql_data_type
+      NULL,          // expected_date_time_sub
+      8,             // expected_octet_char_length
+      2,             // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 3rd Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(this->stmt,
+                        std::wstring(L"$scratch"),          // expected_schema
+                        std::wstring(L"ODBCTest"),          // expected_table
+                        std::wstring(L"decimal_positive"),  // expected_column
+                        SQL_DECIMAL,                        // 
expected_data_type
+                        std::wstring(L"DECIMAL"),           // 
expected_type_name
+                        38,                                 // 
expected_column_size
+                        19,                                 // 
expected_buffer_length
+                        0,                                  // 
expected_decimal_digits
+                        10,                                 // 
expected_num_prec_radix
+                        SQL_NULLABLE,                       // 
expected_nullable
+                        SQL_DECIMAL,                        // 
expected_sql_data_type
+                        NULL,                               // 
expected_date_time_sub
+                        2,                                  // 
expected_octet_char_length
+                        3,                                  // 
expected_ordinal_position
+                        std::wstring(L"YES"));              // 
expected_is_nullable
+
+  // Check 4th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(this->stmt,
+                        std::wstring(L"$scratch"),   // expected_schema
+                        std::wstring(L"ODBCTest"),   // expected_table
+                        std::wstring(L"float_max"),  // expected_column
+                        SQL_FLOAT,                   // expected_data_type
+                        std::wstring(L"FLOAT"),      // expected_type_name
+                        24,  // expected_column_size (precision bits from IEEE 
754)
+                        8,   // expected_buffer_length
+                        0,   // expected_decimal_digits
+                        2,   // expected_num_prec_radix
+                        SQL_NULLABLE,           // expected_nullable
+                        SQL_FLOAT,              // expected_sql_data_type
+                        NULL,                   // expected_date_time_sub
+                        8,                      // expected_octet_char_length
+                        4,                      // expected_ordinal_position
+                        std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 5th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(this->stmt,
+                        std::wstring(L"$scratch"),    // expected_schema
+                        std::wstring(L"ODBCTest"),    // expected_table
+                        std::wstring(L"double_max"),  // expected_column
+                        SQL_DOUBLE,                   // expected_data_type
+                        std::wstring(L"DOUBLE"),      // expected_type_name
+                        53,  // expected_column_size (precision bits from IEEE 
754)
+                        8,   // expected_buffer_length
+                        0,   // expected_decimal_digits
+                        2,   // expected_num_prec_radix
+                        SQL_NULLABLE,           // expected_nullable
+                        SQL_DOUBLE,             // expected_sql_data_type
+                        NULL,                   // expected_date_time_sub
+                        8,                      // expected_octet_char_length
+                        5,                      // expected_ordinal_position
+                        std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 6th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(this->stmt,
+                        std::wstring(L"$scratch"),  // expected_schema
+                        std::wstring(L"ODBCTest"),  // expected_table
+                        std::wstring(L"bit_true"),  // expected_column
+                        SQL_BIT,                    // expected_data_type
+                        std::wstring(L"BOOLEAN"),   // expected_type_name
+                        0,  // expected_column_size (limitation: remote server 
remote
+                            // server returns 0, should be 1)
+                        1,  // expected_buffer_length
+                        0,  // expected_decimal_digits
+                        0,  // expected_num_prec_radix
+                        SQL_NULLABLE,           // expected_nullable
+                        SQL_BIT,                // expected_sql_data_type
+                        NULL,                   // expected_date_time_sub
+                        1,                      // expected_octet_char_length
+                        6,                      // expected_ordinal_position
+                        std::wstring(L"YES"));  // expected_is_nullable
+
+  // ODBC ver 3 returns SQL_TYPE_DATE, SQL_TYPE_TIME, and SQL_TYPE_TIMESTAMP 
in the
+  // DATA_TYPE field
+
+  // Check 7th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),  // expected_schema
+      std::wstring(L"ODBCTest"),  // expected_table
+      std::wstring(L"date_max"),  // expected_column
+      SQL_TYPE_DATE,              // expected_data_type
+      std::wstring(L"DATE"),      // expected_type_name
+      0,   // expected_column_size (limitation: remote server returns 0, 
should be 10)
+      10,  // expected_buffer_length
+      0,   // expected_decimal_digits
+      0,   // expected_num_prec_radix
+      SQL_NULLABLE,           // expected_nullable
+      SQL_DATETIME,           // expected_sql_data_type
+      SQL_CODE_DATE,          // expected_date_time_sub
+      6,                      // expected_octet_char_length
+      7,                      // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 8th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),  // expected_schema
+      std::wstring(L"ODBCTest"),  // expected_table
+      std::wstring(L"time_max"),  // expected_column
+      SQL_TYPE_TIME,              // expected_data_type
+      std::wstring(L"TIME"),      // expected_type_name
+      3,              // expected_column_size (limitation: should be 
9+fractional digits)
+      12,             // expected_buffer_length
+      0,              // expected_decimal_digits
+      0,              // expected_num_prec_radix
+      SQL_NULLABLE,   // expected_nullable
+      SQL_DATETIME,   // expected_sql_data_type
+      SQL_CODE_TIME,  // expected_date_time_sub
+      6,              // expected_octet_char_length
+      8,              // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 9th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),       // expected_schema
+      std::wstring(L"ODBCTest"),       // expected_table
+      std::wstring(L"timestamp_max"),  // expected_column
+      SQL_TYPE_TIMESTAMP,              // expected_data_type
+      std::wstring(L"TIMESTAMP"),      // expected_type_name
+      3,             // expected_column_size (limitation: should be 
20+fractional digits)
+      23,            // expected_buffer_length
+      0,             // expected_decimal_digits
+      0,             // expected_num_prec_radix
+      SQL_NULLABLE,  // expected_nullable
+      SQL_DATETIME,  // expected_sql_data_type
+      SQL_CODE_TIMESTAMP,     // expected_date_time_sub
+      16,                     // expected_octet_char_length
+      9,                      // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // There is no more column
+  EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
+}
+
+TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColumnsAllTypesODBCVer2) {
+  // GH-47159 TODO: Return NUM_PREC_RADIX based on whether COLUMN_SIZE 
contains number of
+  // digits or bits
+
+  SQLWCHAR table_pattern[] = L"ODBCTest";
+  SQLWCHAR column_pattern[] = L"%";
+
+  ASSERT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                    table_pattern, SQL_NTS, column_pattern, 
SQL_NTS));
+
+  // Check 1st Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),      // expected_schema
+      std::wstring(L"ODBCTest"),      // expected_table
+      std::wstring(L"sinteger_max"),  // expected_column
+      SQL_INTEGER,                    // expected_data_type
+      std::wstring(L"INTEGER"),       // expected_type_name
+      32,            // expected_column_size (remote server returns number of 
bits)
+      4,             // expected_buffer_length
+      0,             // expected_decimal_digits
+      10,            // expected_num_prec_radix
+      SQL_NULLABLE,  // expected_nullable
+      SQL_INTEGER,   // expected_sql_data_type
+      NULL,          // expected_date_time_sub
+      4,             // expected_octet_char_length
+      1,             // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 2nd Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),     // expected_schema
+      std::wstring(L"ODBCTest"),     // expected_table
+      std::wstring(L"sbigint_max"),  // expected_column
+      SQL_BIGINT,                    // expected_data_type
+      std::wstring(L"BIGINT"),       // expected_type_name
+      64,            // expected_column_size (remote server returns number of 
bits)
+      8,             // expected_buffer_length
+      0,             // expected_decimal_digits
+      10,            // expected_num_prec_radix
+      SQL_NULLABLE,  // expected_nullable
+      SQL_BIGINT,    // expected_sql_data_type
+      NULL,          // expected_date_time_sub
+      8,             // expected_octet_char_length
+      2,             // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 3rd Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(this->stmt,
+                        std::wstring(L"$scratch"),          // expected_schema
+                        std::wstring(L"ODBCTest"),          // expected_table
+                        std::wstring(L"decimal_positive"),  // expected_column
+                        SQL_DECIMAL,                        // 
expected_data_type
+                        std::wstring(L"DECIMAL"),           // 
expected_type_name
+                        38,                                 // 
expected_column_size
+                        19,                                 // 
expected_buffer_length
+                        0,                                  // 
expected_decimal_digits
+                        10,                                 // 
expected_num_prec_radix
+                        SQL_NULLABLE,                       // 
expected_nullable
+                        SQL_DECIMAL,                        // 
expected_sql_data_type
+                        NULL,                               // 
expected_date_time_sub
+                        2,                                  // 
expected_octet_char_length
+                        3,                                  // 
expected_ordinal_position
+                        std::wstring(L"YES"));              // 
expected_is_nullable
+
+  // Check 4th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(this->stmt,
+                        std::wstring(L"$scratch"),   // expected_schema
+                        std::wstring(L"ODBCTest"),   // expected_table
+                        std::wstring(L"float_max"),  // expected_column
+                        SQL_FLOAT,                   // expected_data_type
+                        std::wstring(L"FLOAT"),      // expected_type_name
+                        24,  // expected_column_size (precision bits from IEEE 
754)
+                        8,   // expected_buffer_length
+                        0,   // expected_decimal_digits
+                        2,   // expected_num_prec_radix
+                        SQL_NULLABLE,           // expected_nullable
+                        SQL_FLOAT,              // expected_sql_data_type
+                        NULL,                   // expected_date_time_sub
+                        8,                      // expected_octet_char_length
+                        4,                      // expected_ordinal_position
+                        std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 5th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(this->stmt,
+                        std::wstring(L"$scratch"),    // expected_schema
+                        std::wstring(L"ODBCTest"),    // expected_table
+                        std::wstring(L"double_max"),  // expected_column
+                        SQL_DOUBLE,                   // expected_data_type
+                        std::wstring(L"DOUBLE"),      // expected_type_name
+                        53,  // expected_column_size (precision bits from IEEE 
754)
+                        8,   // expected_buffer_length
+                        0,   // expected_decimal_digits
+                        2,   // expected_num_prec_radix
+                        SQL_NULLABLE,           // expected_nullable
+                        SQL_DOUBLE,             // expected_sql_data_type
+                        NULL,                   // expected_date_time_sub
+                        8,                      // expected_octet_char_length
+                        5,                      // expected_ordinal_position
+                        std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 6th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(this->stmt,
+                        std::wstring(L"$scratch"),  // expected_schema
+                        std::wstring(L"ODBCTest"),  // expected_table
+                        std::wstring(L"bit_true"),  // expected_column
+                        SQL_BIT,                    // expected_data_type
+                        std::wstring(L"BOOLEAN"),   // expected_type_name
+                        0,  // expected_column_size (limitation: remote server 
remote
+                            // server returns 0, should be 1)
+                        1,  // expected_buffer_length
+                        0,  // expected_decimal_digits
+                        0,  // expected_num_prec_radix
+                        SQL_NULLABLE,           // expected_nullable
+                        SQL_BIT,                // expected_sql_data_type
+                        NULL,                   // expected_date_time_sub
+                        1,                      // expected_octet_char_length
+                        6,                      // expected_ordinal_position
+                        std::wstring(L"YES"));  // expected_is_nullable
+
+  // ODBC ver 2 returns SQL_DATE, SQL_TIME, and SQL_TIMESTAMP in the DATA_TYPE 
field
+
+  // Check 7th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),  // expected_schema
+      std::wstring(L"ODBCTest"),  // expected_table
+      std::wstring(L"date_max"),  // expected_column
+      SQL_DATE,                   // expected_data_type
+      std::wstring(L"DATE"),      // expected_type_name
+      0,   // expected_column_size (limitation: remote server returns 0, 
should be 10)
+      10,  // expected_buffer_length
+      0,   // expected_decimal_digits
+      0,   // expected_num_prec_radix
+      SQL_NULLABLE,           // expected_nullable
+      SQL_DATETIME,           // expected_sql_data_type
+      SQL_CODE_DATE,          // expected_date_time_sub
+      6,                      // expected_octet_char_length
+      7,                      // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 8th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),  // expected_schema
+      std::wstring(L"ODBCTest"),  // expected_table
+      std::wstring(L"time_max"),  // expected_column
+      SQL_TIME,                   // expected_data_type
+      std::wstring(L"TIME"),      // expected_type_name
+      3,              // expected_column_size (limitation: should be 
9+fractional digits)
+      12,             // expected_buffer_length
+      0,              // expected_decimal_digits
+      0,              // expected_num_prec_radix
+      SQL_NULLABLE,   // expected_nullable
+      SQL_DATETIME,   // expected_sql_data_type
+      SQL_CODE_TIME,  // expected_date_time_sub
+      6,              // expected_octet_char_length
+      8,              // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 9th Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckRemoteSQLColumns(
+      this->stmt,
+      std::wstring(L"$scratch"),       // expected_schema
+      std::wstring(L"ODBCTest"),       // expected_table
+      std::wstring(L"timestamp_max"),  // expected_column
+      SQL_TIMESTAMP,                   // expected_data_type
+      std::wstring(L"TIMESTAMP"),      // expected_type_name
+      3,             // expected_column_size (limitation: should be 
20+fractional digits)
+      23,            // expected_buffer_length
+      0,             // expected_decimal_digits
+      0,             // expected_num_prec_radix
+      SQL_NULLABLE,  // expected_nullable
+      SQL_DATETIME,  // expected_sql_data_type
+      SQL_CODE_TIMESTAMP,     // expected_date_time_sub
+      16,                     // expected_octet_char_length
+      9,                      // expected_ordinal_position
+      std::wstring(L"YES"));  // expected_is_nullable
+
+  // There is no more column
+  EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
+}
+
+TEST_F(ColumnsMockTest, TestSQLColumnsColumnPattern) {
+  // Checks filtering table with column name pattern.
+  // Only check table and column name
+
+  SQLWCHAR table_pattern[] = L"%";
+  SQLWCHAR column_pattern[] = L"id";
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                    table_pattern, SQL_NTS, column_pattern, 
SQL_NTS));
+
+  // Check 1st Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),          // expected_catalog
+                      std::wstring(L"foreignTable"),  // expected_table
+                      std::wstring(L"id"),            // expected_column
+                      SQL_BIGINT,                     // expected_data_type
+                      std::wstring(L"BIGINT"),        // expected_type_name
+                      10,  // expected_column_size (mock returns 10 instead of 
19)
+                      8,   // expected_buffer_length
+                      15,  // expected_decimal_digits (mock returns 15 instead 
of 0)
+                      10,  // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BIGINT,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      8,                      // expected_octet_char_length
+                      1,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // Check 2nd Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),      // expected_catalog
+                      std::wstring(L"intTable"),  // expected_table
+                      std::wstring(L"id"),        // expected_column
+                      SQL_BIGINT,                 // expected_data_type
+                      std::wstring(L"BIGINT"),    // expected_type_name
+                      10,  // expected_column_size (mock returns 10 instead of 
19)
+                      8,   // expected_buffer_length
+                      15,  // expected_decimal_digits (mock returns 15 instead 
of 0)
+                      10,  // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BIGINT,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      8,                      // expected_octet_char_length
+                      1,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // There is no more column
+  EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
+}
+
+TEST_F(ColumnsMockTest, TestSQLColumnsTableColumnPattern) {
+  // Checks filtering table with table and column name pattern.
+  // Only check table and column name
+
+  SQLWCHAR table_pattern[] = L"foreignTable";
+  SQLWCHAR column_pattern[] = L"id";
+
+  ASSERT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                    table_pattern, SQL_NTS, column_pattern, 
SQL_NTS));
+
+  // Check 1st Column
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  CheckMockSQLColumns(this->stmt,
+                      std::wstring(L"main"),          // expected_catalog
+                      std::wstring(L"foreignTable"),  // expected_table
+                      std::wstring(L"id"),            // expected_column
+                      SQL_BIGINT,                     // expected_data_type
+                      std::wstring(L"BIGINT"),        // expected_type_name
+                      10,  // expected_column_size (mock returns 10 instead of 
19)
+                      8,   // expected_buffer_length
+                      15,  // expected_decimal_digits (mock returns 15 instead 
of 0)
+                      10,  // expected_num_prec_radix
+                      SQL_NULLABLE,           // expected_nullable
+                      SQL_BIGINT,             // expected_sql_data_type
+                      NULL,                   // expected_date_time_sub
+                      8,                      // expected_octet_char_length
+                      1,                      // expected_ordinal_position
+                      std::wstring(L"YES"));  // expected_is_nullable
+
+  // There is no more column
+  EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
+}
+
+TEST_F(ColumnsMockTest, TestSQLColumnsInvalidTablePattern) {
+  SQLWCHAR table_pattern[] = L"non-existent-table";
+  SQLWCHAR column_pattern[] = L"%";
+
+  ASSERT_EQ(SQL_SUCCESS, SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                    table_pattern, SQL_NTS, column_pattern, 
SQL_NTS));
+
+  // There is no column from filter
+  EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
+}
+
+}  // namespace arrow::flight::sql::odbc
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc 
b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
index e50b552175..3f12e35c6d 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
@@ -180,9 +180,20 @@ void FlightSQLOdbcV2RemoteTestBase::SetUp() {
   connected_ = true;
 }
 
-void FlightSQLOdbcEnvConnHandleRemoteTestBase::SetUp() { 
AllocEnvConnHandles(); }
+void FlightSQLOdbcEnvConnHandleRemoteTestBase::SetUp() {
+  ODBCRemoteTestBase::SetUp();
+  if (skipping_test_) {
+    return;
+  }
+
+  AllocEnvConnHandles();
+}
 
 void FlightSQLOdbcEnvConnHandleRemoteTestBase::TearDown() {
+  if (skipping_test_) {
+    return;
+  }
+
   // Free connection handle
   EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DBC, conn));
 

Reply via email to