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 16de220388 GH-47721: [C++][FlightRPC] Return ODBC Column Attribute 
from result set (#48050)
16de220388 is described below

commit 16de22038825b2f22687ee873d0240c619da2db2
Author: Alina (Xi) Li <[email protected]>
AuthorDate: Mon Dec 29 17:07:52 2025 -0800

    GH-47721: [C++][FlightRPC] Return ODBC Column Attribute from result set 
(#48050)
    
    ### Rationale for this change
    Implement ODBC to return column attribute values from the result set
    
    ### What changes are included in this PR?
    - SQLColAttribute & Tests
    ### Are these changes tested?
    Tested on local MSVC
    ### Are there any user-facing changes?
    N/A
    * GitHub Issue: #47721
    
    Authored-by: Alina (Xi) Li <[email protected]>
    Signed-off-by: David Li <[email protected]>
---
 cpp/src/arrow/flight/sql/column_metadata.cc        |    6 +-
 cpp/src/arrow/flight/sql/odbc/odbc_api.cc          |   86 +-
 .../odbc_impl/flight_sql_result_set_metadata.cc    |   32 +-
 .../odbc_impl/flight_sql_result_set_metadata.h     |    3 +-
 .../flight/sql/odbc/odbc_impl/odbc_descriptor.cc   |   62 +-
 .../sql/odbc/odbc_impl/spi/result_set_metadata.h   |    6 +-
 .../arrow/flight/sql/odbc/tests/columns_test.cc    | 1224 ++++++++++++++++++++
 7 files changed, 1365 insertions(+), 54 deletions(-)

diff --git a/cpp/src/arrow/flight/sql/column_metadata.cc 
b/cpp/src/arrow/flight/sql/column_metadata.cc
index 30f557084b..6501e3c716 100644
--- a/cpp/src/arrow/flight/sql/column_metadata.cc
+++ b/cpp/src/arrow/flight/sql/column_metadata.cc
@@ -58,8 +58,10 @@ const char* ColumnMetadata::kIsSearchable = 
"ARROW:FLIGHT:SQL:IS_SEARCHABLE";
 const char* ColumnMetadata::kRemarks = "ARROW:FLIGHT:SQL:REMARKS";
 
 ColumnMetadata::ColumnMetadata(
-    std::shared_ptr<const arrow::KeyValueMetadata> metadata_map)
-    : metadata_map_(std::move(metadata_map)) {}
+    std::shared_ptr<const arrow::KeyValueMetadata> metadata_map) {
+  metadata_map_ = std::move(metadata_map ? metadata_map
+                                         : 
std::make_shared<arrow::KeyValueMetadata>());
+}
 
 arrow::Result<std::string> ColumnMetadata::GetCatalogName() const {
   return metadata_map_->Get(kCatalogName);
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index 9ed0d29259..6ce248a4fa 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -1297,8 +1297,90 @@ SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT 
record_number,
                    << ", output_length: " << static_cast<const 
void*>(output_length)
                    << ", numeric_attribute_ptr: "
                    << static_cast<const void*>(numeric_attribute_ptr);
-  // GH-47721 TODO: Implement SQLColAttribute, pre-requisite requires 
SQLColumns
-  return SQL_INVALID_HANDLE;
+
+  using ODBC::ODBCDescriptor;
+  using ODBC::ODBCStatement;
+  return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
+    ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
+    ODBCDescriptor* ird = statement->GetIRD();
+    SQLINTEGER output_length_int;
+    switch (field_identifier) {
+      // Numeric attributes
+      // internal is SQLLEN, no conversion is needed
+      case SQL_DESC_DISPLAY_SIZE:
+      case SQL_DESC_OCTET_LENGTH: {
+        ird->GetField(record_number, field_identifier, numeric_attribute_ptr,
+                      buffer_length, &output_length_int);
+        break;
+      }
+      // internal is SQLULEN, conversion is needed.
+      case SQL_COLUMN_LENGTH:  // ODBC 2.0
+      case SQL_DESC_LENGTH: {
+        SQLULEN temp;
+        ird->GetField(record_number, field_identifier, &temp, buffer_length,
+                      &output_length_int);
+        if (numeric_attribute_ptr) {
+          *numeric_attribute_ptr = static_cast<SQLLEN>(temp);
+        }
+        break;
+      }
+      // internal is SQLINTEGER, conversion is needed.
+      case SQL_DESC_AUTO_UNIQUE_VALUE:
+      case SQL_DESC_CASE_SENSITIVE:
+      case SQL_DESC_NUM_PREC_RADIX: {
+        SQLINTEGER temp;
+        ird->GetField(record_number, field_identifier, &temp, buffer_length,
+                      &output_length_int);
+        if (numeric_attribute_ptr) {
+          *numeric_attribute_ptr = static_cast<SQLLEN>(temp);
+        }
+        break;
+      }
+      // internal is SQLSMALLINT, conversion is needed.
+      case SQL_DESC_CONCISE_TYPE:
+      case SQL_DESC_COUNT:
+      case SQL_DESC_FIXED_PREC_SCALE:
+      case SQL_DESC_TYPE:
+      case SQL_DESC_NULLABLE:
+      case SQL_COLUMN_PRECISION:  // ODBC 2.0
+      case SQL_DESC_PRECISION:
+      case SQL_COLUMN_SCALE:  // ODBC 2.0
+      case SQL_DESC_SCALE:
+      case SQL_DESC_SEARCHABLE:
+      case SQL_DESC_UNNAMED:
+      case SQL_DESC_UNSIGNED:
+      case SQL_DESC_UPDATABLE: {
+        SQLSMALLINT temp;
+        ird->GetField(record_number, field_identifier, &temp, buffer_length,
+                      &output_length_int);
+        if (numeric_attribute_ptr) {
+          *numeric_attribute_ptr = static_cast<SQLLEN>(temp);
+        }
+        break;
+      }
+      // Character attributes
+      case SQL_DESC_BASE_COLUMN_NAME:
+      case SQL_DESC_BASE_TABLE_NAME:
+      case SQL_DESC_CATALOG_NAME:
+      case SQL_DESC_LABEL:
+      case SQL_DESC_LITERAL_PREFIX:
+      case SQL_DESC_LITERAL_SUFFIX:
+      case SQL_DESC_LOCAL_TYPE_NAME:
+      case SQL_DESC_NAME:
+      case SQL_DESC_SCHEMA_NAME:
+      case SQL_DESC_TABLE_NAME:
+      case SQL_DESC_TYPE_NAME:
+        ird->GetField(record_number, field_identifier, character_attribute_ptr,
+                      buffer_length, &output_length_int);
+        break;
+      default:
+        throw DriverException("Invalid descriptor field", "HY091");
+    }
+    if (output_length) {
+      *output_length = static_cast<SQLSMALLINT>(output_length_int);
+    }
+    return SQL_SUCCESS;
+  });
 }
 
 SQLRETURN SQLGetTypeInfo(SQLHSTMT stmt, SQLSMALLINT data_type) {
diff --git 
a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.cc
index 8ac3c7ed75..11f6c60080 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.cc
@@ -20,6 +20,8 @@
 #include "arrow/flight/sql/column_metadata.h"
 #include "arrow/flight/sql/odbc/odbc_impl/platform.h"
 #include "arrow/flight/sql/odbc/odbc_impl/util.h"
+#include "arrow/type_traits.h"
+#include "arrow/util/key_value_metadata.h"
 
 #include <utility>
 #include "arrow/flight/sql/odbc/odbc_impl/exceptions.h"
@@ -40,12 +42,8 @@ constexpr int32_t DefaultDecimalPrecision = 38;
 constexpr int32_t DefaultLengthForVariableLengthColumns = 1024;
 
 namespace {
-std::shared_ptr<const KeyValueMetadata> empty_metadata_map(new 
KeyValueMetadata);
-
 inline ColumnMetadata GetMetadata(const std::shared_ptr<Field>& field) {
-  const auto& metadata_map = field->metadata();
-
-  ColumnMetadata metadata(metadata_map ? metadata_map : empty_metadata_map);
+  ColumnMetadata metadata(field->metadata());
   return metadata;
 }
 
@@ -207,10 +205,14 @@ size_t FlightSqlResultSetMetadata::GetOctetLength(int 
column_position) {
       .value_or(DefaultLengthForVariableLengthColumns);
 }
 
-std::string FlightSqlResultSetMetadata::GetTypeName(int column_position) {
+std::string FlightSqlResultSetMetadata::GetTypeName(int column_position,
+                                                    int16_t data_type) {
   ColumnMetadata metadata = GetMetadata(schema_->field(column_position - 1));
 
-  return metadata.GetTypeName().ValueOrElse([] { return ""; });
+  return metadata.GetTypeName().ValueOrElse([data_type] {
+    // If we get an empty type name, figure out the type name from the 
data_type.
+    return util::GetTypeNameFromSqlDataType(data_type);
+  });
 }
 
 Updatability FlightSqlResultSetMetadata::GetUpdatable(int column_position) {
@@ -239,20 +241,14 @@ Searchability 
FlightSqlResultSetMetadata::IsSearchable(int column_position) {
 
 bool FlightSqlResultSetMetadata::IsUnsigned(int column_position) {
   const std::shared_ptr<Field>& field = schema_->field(column_position - 1);
-
-  switch (field->type()->id()) {
-    case Type::UINT8:
-    case Type::UINT16:
-    case Type::UINT32:
-    case Type::UINT64:
-      return true;
-    default:
-      return false;
-  }
+  arrow::Type::type type_id = field->type()->id();
+  // non-decimal and non-numeric types are unsigned.
+  return !arrow::is_decimal(type_id) &&
+         (!arrow::is_numeric(type_id) || arrow::is_unsigned_integer(type_id));
 }
 
 bool FlightSqlResultSetMetadata::IsFixedPrecScale(int column_position) {
-  // TODO: Flight SQL column metadata does not have this, should we add to the 
spec?
+  // Precision for Arrow data types are modifiable by the user
   return false;
 }
 
diff --git 
a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.h 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.h
index 0d141a4bb9..b502494f71 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.h
@@ -77,7 +77,7 @@ class FlightSqlResultSetMetadata : public ResultSetMetadata {
 
   size_t GetOctetLength(int column_position) override;
 
-  std::string GetTypeName(int column_position) override;
+  std::string GetTypeName(int column_position, int16_t data_type) override;
 
   Updatability GetUpdatable(int column_position) override;
 
@@ -87,6 +87,7 @@ class FlightSqlResultSetMetadata : public ResultSetMetadata {
 
   Searchability IsSearchable(int column_position) override;
 
+  /// \brief Return true if the column is unsigned or not numeric
   bool IsUnsigned(int column_position) override;
 
   bool IsFixedPrecScale(int column_position) override;
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.cc
index d2b7f8865c..eae90ac2b7 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.cc
@@ -276,7 +276,9 @@ void ODBCDescriptor::GetHeaderField(SQLSMALLINT 
field_identifier, SQLPOINTER val
       GetAttribute(rows_processed_ptr_, value, buffer_length, output_length);
       break;
     case SQL_DESC_COUNT: {
-      GetAttribute(highest_one_based_bound_record_, value, buffer_length, 
output_length);
+      // highest_one_based_bound_record_ equals number of records + 1
+      GetAttribute(static_cast<SQLSMALLINT>(highest_one_based_bound_record_ - 
1), value,
+                   buffer_length, output_length);
       break;
     }
     default:
@@ -310,54 +312,55 @@ void ODBCDescriptor::GetField(SQLSMALLINT record_number, 
SQLSMALLINT field_ident
     throw DriverException("Invalid descriptor index", "07009");
   }
 
-  // TODO: Restrict fields based on AppDescriptor IPD, and IRD.
+  // GH-47867 TODO: Restrict fields based on AppDescriptor IPD, and IRD.
 
+  bool length_in_bytes = true;
   SQLSMALLINT zero_based_record = record_number - 1;
   const DescriptorRecord& record = records_[zero_based_record];
   switch (field_identifier) {
     case SQL_DESC_BASE_COLUMN_NAME:
-      GetAttributeUTF8(record.base_column_name, value, buffer_length, 
output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.base_column_name, length_in_bytes, value, 
buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_BASE_TABLE_NAME:
-      GetAttributeUTF8(record.base_table_name, value, buffer_length, 
output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.base_table_name, length_in_bytes, value, 
buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_CATALOG_NAME:
-      GetAttributeUTF8(record.catalog_name, value, buffer_length, 
output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.catalog_name, length_in_bytes, value, 
buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_LABEL:
-      GetAttributeUTF8(record.label, value, buffer_length, output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.label, length_in_bytes, value, buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_LITERAL_PREFIX:
-      GetAttributeUTF8(record.literal_prefix, value, buffer_length, 
output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.literal_prefix, length_in_bytes, value, 
buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_LITERAL_SUFFIX:
-      GetAttributeUTF8(record.literal_suffix, value, buffer_length, 
output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.literal_suffix, length_in_bytes, value, 
buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_LOCAL_TYPE_NAME:
-      GetAttributeUTF8(record.local_type_name, value, buffer_length, 
output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.local_type_name, length_in_bytes, value, 
buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_NAME:
-      GetAttributeUTF8(record.name, value, buffer_length, output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.name, length_in_bytes, value, buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_SCHEMA_NAME:
-      GetAttributeUTF8(record.schema_name, value, buffer_length, output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.schema_name, length_in_bytes, value, 
buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_TABLE_NAME:
-      GetAttributeUTF8(record.table_name, value, buffer_length, output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.table_name, length_in_bytes, value, 
buffer_length,
+                           output_length, GetDiagnostics());
       break;
     case SQL_DESC_TYPE_NAME:
-      GetAttributeUTF8(record.type_name, value, buffer_length, output_length,
-                       GetDiagnostics());
+      GetAttributeSQLWCHAR(record.type_name, length_in_bytes, value, 
buffer_length,
+                           output_length, GetDiagnostics());
       break;
 
     case SQL_DESC_DATA_PTR:
@@ -367,7 +370,7 @@ void ODBCDescriptor::GetField(SQLSMALLINT record_number, 
SQLSMALLINT field_ident
     case SQL_DESC_OCTET_LENGTH_PTR:
       GetAttribute(record.indicator_ptr, value, buffer_length, output_length);
       break;
-
+    case SQL_COLUMN_LENGTH:  // ODBC 2.0
     case SQL_DESC_LENGTH:
       GetAttribute(record.length, value, buffer_length, output_length);
       break;
@@ -407,12 +410,14 @@ void ODBCDescriptor::GetField(SQLSMALLINT record_number, 
SQLSMALLINT field_ident
     case SQL_DESC_PARAMETER_TYPE:
       GetAttribute(record.param_type, value, buffer_length, output_length);
       break;
+    case SQL_COLUMN_PRECISION:  // ODBC 2.0
     case SQL_DESC_PRECISION:
       GetAttribute(record.precision, value, buffer_length, output_length);
       break;
     case SQL_DESC_ROWVER:
       GetAttribute(record.row_ver, value, buffer_length, output_length);
       break;
+    case SQL_COLUMN_SCALE:  // ODBC 2.0
     case SQL_DESC_SCALE:
       GetAttribute(record.scale, value, buffer_length, output_length);
       break;
@@ -479,6 +484,8 @@ void 
ODBCDescriptor::PopulateFromResultSetMetadata(ResultSetMetadata* rsmd) {
 
   for (size_t i = 0; i < records_.size(); ++i) {
     size_t one_based_index = i + 1;
+    int16_t concise_type = rsmd->GetConciseType(one_based_index);
+
     records_[i].base_column_name = rsmd->GetBaseColumnName(one_based_index);
     records_[i].base_table_name = rsmd->GetBaseTableName(one_based_index);
     records_[i].catalog_name = rsmd->GetCatalogName(one_based_index);
@@ -489,9 +496,8 @@ void 
ODBCDescriptor::PopulateFromResultSetMetadata(ResultSetMetadata* rsmd) {
     records_[i].name = rsmd->GetName(one_based_index);
     records_[i].schema_name = rsmd->GetSchemaName(one_based_index);
     records_[i].table_name = rsmd->GetTableName(one_based_index);
-    records_[i].type_name = rsmd->GetTypeName(one_based_index);
-    records_[i].concise_type = GetSqlTypeForODBCVersion(
-        rsmd->GetConciseType(one_based_index), is_2x_connection_);
+    records_[i].type_name = rsmd->GetTypeName(one_based_index, concise_type);
+    records_[i].concise_type = GetSqlTypeForODBCVersion(concise_type, 
is_2x_connection_);
     records_[i].data_ptr = nullptr;
     records_[i].indicator_ptr = nullptr;
     records_[i].display_size = rsmd->GetColumnDisplaySize(one_based_index);
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set_metadata.h 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set_metadata.h
index 38f81fc9c3..63da402db7 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set_metadata.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set_metadata.h
@@ -17,9 +17,8 @@
 
 #pragma once
 
-#include "arrow/flight/sql/odbc/odbc_impl/types.h"
-
 #include <string>
+#include "arrow/flight/sql/odbc/odbc_impl/types.h"
 
 namespace arrow::flight::sql::odbc {
 
@@ -143,8 +142,9 @@ class ResultSetMetadata {
 
   /// \brief It returns the data type as a string.
   /// \param column_position [in] the position of the column, starting from 1.
+  /// \param data_type [in] the data type of the column.
   /// \return the data type string.
-  virtual std::string GetTypeName(int column_position) = 0;
+  virtual std::string GetTypeName(int column_position, int16_t data_type) = 0;
 
   /// \brief It returns a numeric values indicate the updatability of the
   /// column.
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc 
b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc
index 81e97a0928..87e9193b7c 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc
@@ -124,6 +124,260 @@ void CheckRemoteSQLColumns(
                   expected_is_nullable);
 }
 
+void CheckSQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT idx,
+                          const std::wstring& expected_column_name,
+                          SQLLEN expected_data_type, SQLLEN 
expected_concise_type,
+                          SQLLEN expected_display_size, SQLLEN 
expected_prec_scale,
+                          SQLLEN expected_length,
+                          const std::wstring& expected_literal_prefix,
+                          const std::wstring& expected_literal_suffix,
+                          SQLLEN expected_column_size, SQLLEN 
expected_column_scale,
+                          SQLLEN expected_column_nullability,
+                          SQLLEN expected_num_prec_radix, SQLLEN 
expected_octet_length,
+                          SQLLEN expected_searchable, SQLLEN 
expected_unsigned_column) {
+  std::vector<SQLWCHAR> name(kOdbcBufferSize);
+  SQLSMALLINT name_len = 0;
+  std::vector<SQLWCHAR> base_column_name(kOdbcBufferSize);
+  SQLSMALLINT column_name_len = 0;
+  std::vector<SQLWCHAR> label(kOdbcBufferSize);
+  SQLSMALLINT label_len = 0;
+  std::vector<SQLWCHAR> prefix(kOdbcBufferSize);
+  SQLSMALLINT prefix_len = 0;
+  std::vector<SQLWCHAR> suffix(kOdbcBufferSize);
+  SQLSMALLINT suffix_len = 0;
+  SQLLEN data_type = 0;
+  SQLLEN concise_type = 0;
+  SQLLEN display_size = 0;
+  SQLLEN prec_scale = 0;
+  SQLLEN length = 0;
+  SQLLEN size = 0;
+  SQLLEN scale = 0;
+  SQLLEN nullability = 0;
+  SQLLEN num_prec_radix = 0;
+  SQLLEN octet_length = 0;
+  SQLLEN searchable = 0;
+  SQLLEN unsigned_col = 0;
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_NAME, &name[0],
+                                         (SQLSMALLINT)name.size(), &name_len, 
nullptr));
+
+  EXPECT_EQ(
+      SQL_SUCCESS,
+      SQLColAttribute(stmt, idx, SQL_DESC_BASE_COLUMN_NAME, 
&base_column_name[0],
+                      (SQLSMALLINT)base_column_name.size(), &column_name_len, 
nullptr));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_LABEL, &label[0],
+                                         (SQLSMALLINT)label.size(), 
&label_len, nullptr));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_DESC_TYPE, 0, 0, nullptr, 
&data_type));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_CONCISE_TYPE, 0, 
0, nullptr,
+                                         &concise_type));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_DISPLAY_SIZE, 0, 
0, nullptr,
+                                         &display_size));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_FIXED_PREC_SCALE, 
0, 0,
+                                         nullptr, &prec_scale));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_DESC_LENGTH, 0, 0, nullptr, 
&length));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_DESC_LITERAL_PREFIX, &prefix[0],
+                            (SQLSMALLINT)prefix.size(), &prefix_len, nullptr));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_DESC_LITERAL_SUFFIX, &suffix[0],
+                            (SQLSMALLINT)suffix.size(), &suffix_len, nullptr));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_DESC_PRECISION, 0, 0, nullptr, 
&size));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_DESC_SCALE, 0, 0, nullptr, &scale));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_DESC_NULLABLE, 0, 0, nullptr, 
&nullability));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_NUM_PREC_RADIX, 
0, 0, 0,
+                                         &num_prec_radix));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_OCTET_LENGTH, 0, 
0, nullptr,
+                                         &octet_length));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_DESC_SEARCHABLE, 0, 0, nullptr, 
&searchable));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_DESC_UNSIGNED, 0, 0, nullptr, 
&unsigned_col));
+
+  std::wstring name_str = ConvertToWString(name, name_len);
+  std::wstring base_column_name_str = ConvertToWString(base_column_name, 
column_name_len);
+  std::wstring label_str = ConvertToWString(label, label_len);
+  std::wstring prefixStr = ConvertToWString(prefix, prefix_len);
+
+  // Assume column name, base column name, and label are equivalent in the 
result set
+  EXPECT_EQ(expected_column_name, name_str);
+  EXPECT_EQ(expected_column_name, base_column_name_str);
+  EXPECT_EQ(expected_column_name, label_str);
+  EXPECT_EQ(expected_data_type, data_type);
+  EXPECT_EQ(expected_concise_type, concise_type);
+  EXPECT_EQ(expected_display_size, display_size);
+  EXPECT_EQ(expected_prec_scale, prec_scale);
+  EXPECT_EQ(expected_length, length);
+  EXPECT_EQ(expected_literal_prefix, prefixStr);
+  EXPECT_EQ(expected_column_size, size);
+  EXPECT_EQ(expected_column_scale, scale);
+  EXPECT_EQ(expected_column_nullability, nullability);
+  EXPECT_EQ(expected_num_prec_radix, num_prec_radix);
+  EXPECT_EQ(expected_octet_length, octet_length);
+  EXPECT_EQ(expected_searchable, searchable);
+  EXPECT_EQ(expected_unsigned_column, unsigned_col);
+}
+
+void CheckSQLColAttributes(SQLHSTMT stmt, SQLUSMALLINT idx,
+                           const std::wstring& expected_column_name,
+                           SQLLEN expected_data_type, SQLLEN 
expected_display_size,
+                           SQLLEN expected_prec_scale, SQLLEN expected_length,
+                           SQLLEN expected_column_size, SQLLEN 
expected_column_scale,
+                           SQLLEN expected_column_nullability, SQLLEN 
expected_searchable,
+                           SQLLEN expected_unsigned_column) {
+  std::vector<SQLWCHAR> name(kOdbcBufferSize);
+  SQLSMALLINT name_len = 0;
+  std::vector<SQLWCHAR> label(kOdbcBufferSize);
+  SQLSMALLINT label_len = 0;
+  SQLLEN data_type = 0;
+  SQLLEN display_size = 0;
+  SQLLEN prec_scale = 0;
+  SQLLEN length = 0;
+  SQLLEN size = 0;
+  SQLLEN scale = 0;
+  SQLLEN nullability = 0;
+  SQLLEN searchable = 0;
+  SQLLEN unsigned_col = 0;
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, SQL_COLUMN_NAME, &name[0],
+                                          (SQLSMALLINT)name.size(), &name_len, 
nullptr));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttributes(stmt, idx, SQL_COLUMN_LABEL, &label[0],
+                             (SQLSMALLINT)label.size(), &label_len, nullptr));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttributes(stmt, idx, SQL_COLUMN_TYPE, 0, 0, nullptr, 
&data_type));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, SQL_COLUMN_DISPLAY_SIZE, 
0, 0,
+                                          nullptr, &display_size));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, SQL_COLUMN_MONEY, 0, 0, nullptr, 
&prec_scale));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttributes(stmt, idx, SQL_COLUMN_LENGTH, 0, 0, nullptr, 
&length));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttributes(stmt, idx, SQL_COLUMN_PRECISION, 0, 0, nullptr, 
&size));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttributes(stmt, idx, SQL_COLUMN_SCALE, 0, 0, nullptr, 
&scale));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, SQL_COLUMN_NULLABLE, 0, 
0, nullptr,
+                                          &nullability));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, SQL_COLUMN_SEARCHABLE, 0, 
0, nullptr,
+                                          &searchable));
+
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, SQL_COLUMN_UNSIGNED, 0, 
0, nullptr,
+                                          &unsigned_col));
+
+  std::wstring name_str = ConvertToWString(name, name_len);
+  std::wstring label_str = ConvertToWString(label, label_len);
+
+  EXPECT_EQ(expected_column_name, name_str);
+  EXPECT_EQ(expected_column_name, label_str);
+  EXPECT_EQ(expected_data_type, data_type);
+  EXPECT_EQ(expected_display_size, display_size);
+  EXPECT_EQ(expected_length, length);
+  EXPECT_EQ(expected_column_size, size);
+  EXPECT_EQ(expected_column_scale, scale);
+  EXPECT_EQ(expected_column_nullability, nullability);
+  EXPECT_EQ(expected_searchable, searchable);
+  EXPECT_EQ(expected_unsigned_column, unsigned_col);
+}
+
+void GetSQLColAttributeString(SQLHSTMT stmt, const std::wstring& wsql, 
SQLUSMALLINT idx,
+                              SQLUSMALLINT field_identifier, std::wstring& 
value) {
+  if (!wsql.empty()) {
+    // Execute query
+    std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+    ASSERT_EQ(SQL_SUCCESS,
+              SQLExecDirect(stmt, &sql0[0], 
static_cast<SQLINTEGER>(sql0.size())));
+
+    ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt));
+  }
+
+  // check SQLColAttribute string attribute
+  std::vector<SQLWCHAR> str_val(kOdbcBufferSize);
+  SQLSMALLINT str_len = 0;
+
+  ASSERT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, field_identifier, 
&str_val[0],
+                                         (SQLSMALLINT)str_val.size(), 
&str_len, nullptr));
+
+  value = ConvertToWString(str_val, str_len);
+}
+
+void GetSQLColAttributesString(SQLHSTMT stmt, const std::wstring& wsql, 
SQLUSMALLINT idx,
+                               SQLUSMALLINT field_identifier, std::wstring& 
value) {
+  if (!wsql.empty()) {
+    // Execute query
+    std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+    ASSERT_EQ(SQL_SUCCESS,
+              SQLExecDirect(stmt, &sql0[0], 
static_cast<SQLINTEGER>(sql0.size())));
+
+    ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt));
+  }
+
+  // check SQLColAttribute string attribute
+  std::vector<SQLWCHAR> str_val(kOdbcBufferSize);
+  SQLSMALLINT str_len = 0;
+
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLColAttributes(stmt, idx, field_identifier, &str_val[0],
+                             (SQLSMALLINT)str_val.size(), &str_len, nullptr));
+
+  value = ConvertToWString(str_val, str_len);
+}
+
+void GetSQLColAttributeNumeric(SQLHSTMT stmt, const std::wstring& wsql, 
SQLUSMALLINT idx,
+                               SQLUSMALLINT field_identifier, SQLLEN* value) {
+  // Execute query and check SQLColAttribute numeric attribute
+  std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLExecDirect(stmt, &sql0[0], 
static_cast<SQLINTEGER>(sql0.size())));
+
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt));
+
+  SQLLEN num_val = 0;
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLColAttribute(stmt, idx, field_identifier, 0, 0, nullptr, 
value));
+}
+
+void GetSQLColAttributesNumeric(SQLHSTMT stmt, const std::wstring& wsql, 
SQLUSMALLINT idx,
+                                SQLUSMALLINT field_identifier, SQLLEN* value) {
+  // Execute query and check SQLColAttribute numeric attribute
+  std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLExecDirect(stmt, &sql0[0], 
static_cast<SQLINTEGER>(sql0.size())));
+
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt));
+
+  SQLLEN num_val = 0;
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLColAttributes(stmt, idx, field_identifier, 0, 0, nullptr, 
value));
+}
+
 }  // namespace
 
 TYPED_TEST(ColumnsTest, SQLColumnsTestInputData) {
@@ -960,4 +1214,974 @@ TEST_F(ColumnsMockTest, 
TestSQLColumnsInvalidTablePattern) {
   EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
 }
 
+TYPED_TEST(ColumnsTest, SQLColAttributeTestInputData) {
+  std::wstring wsql = L"SELECT 1 as col1;";
+  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));
+
+  SQLUSMALLINT idx = 1;
+  std::vector<SQLWCHAR> character_attr(kOdbcBufferSize);
+  SQLSMALLINT character_attr_len = 0;
+  SQLLEN numeric_attr = 0;
+
+  // All character values populated
+  EXPECT_EQ(
+      SQL_SUCCESS,
+      SQLColAttribute(this->stmt, idx, SQL_DESC_NAME, &character_attr[0],
+                      (SQLSMALLINT)character_attr.size(), &character_attr_len, 
nullptr));
+
+  // All numeric values populated
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(this->stmt, idx, SQL_DESC_COUNT, 0, 
0, nullptr,
+                                         &numeric_attr));
+
+  // Pass null values, driver should not throw error
+  EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(this->stmt, idx, 
SQL_COLUMN_TABLE_NAME, 0, 0,
+                                         nullptr, nullptr));
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLColAttribute(this->stmt, idx, SQL_DESC_COUNT, 0, 0, nullptr, 
nullptr));
+}
+
+TYPED_TEST(ColumnsTest, SQLColAttributeGetCharacterLen) {
+  std::wstring wsql = L"SELECT 1 as col1;";
+  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));
+
+  SQLSMALLINT character_attr_len = 0;
+
+  // Check length of character attribute
+  ASSERT_EQ(SQL_SUCCESS, SQLColAttribute(this->stmt, 1, 
SQL_DESC_BASE_COLUMN_NAME, 0, 0,
+                                         &character_attr_len, nullptr));
+  EXPECT_EQ(4 * GetSqlWCharSize(), character_attr_len);
+}
+
+TYPED_TEST(ColumnsTest, SQLColAttributeInvalidFieldId) {
+  std::wstring wsql = L"SELECT 1 as col1;";
+  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));
+
+  SQLUSMALLINT invalid_field_id = -100;
+  SQLUSMALLINT idx = 1;
+  std::vector<SQLWCHAR> character_attr(kOdbcBufferSize);
+  SQLSMALLINT character_attr_len = 0;
+  SQLLEN numeric_attr = 0;
+
+  ASSERT_EQ(
+      SQL_ERROR,
+      SQLColAttribute(this->stmt, idx, invalid_field_id, &character_attr[0],
+                      (SQLSMALLINT)character_attr.size(), &character_attr_len, 
nullptr));
+  // Verify invalid descriptor field identifier error state is returned
+  VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY091);
+}
+
+TYPED_TEST(ColumnsTest, SQLColAttributeInvalidColId) {
+  std::wstring wsql = L"SELECT 1 as col1;";
+  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));
+
+  SQLUSMALLINT invalid_col_id = 2;
+  std::vector<SQLWCHAR> character_attr(kOdbcBufferSize);
+  SQLSMALLINT character_attr_len = 0;
+
+  ASSERT_EQ(SQL_ERROR,
+            SQLColAttribute(this->stmt, invalid_col_id, 
SQL_DESC_BASE_COLUMN_NAME,
+                            &character_attr[0], 
(SQLSMALLINT)character_attr.size(),
+                            &character_attr_len, nullptr));
+  // Verify invalid descriptor index error state is returned
+  VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState07009);
+}
+
+TEST_F(ColumnsMockTest, TestSQLColAttributeAllTypes) {
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  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));
+
+  CheckSQLColAttribute(this->stmt, 1,
+                       std::wstring(L"bigint_col"),  // expected_column_name
+                       SQL_BIGINT,                   // expected_data_type
+                       SQL_BIGINT,                   // expected_concise_type
+                       20,                           // expected_display_size
+                       SQL_FALSE,                    // expected_prec_scale
+                       8,                            // expected_length
+                       std::wstring(L""),            // expected_literal_prefix
+                       std::wstring(L""),            // expected_literal_suffix
+                       8,                            // expected_column_size
+                       0,                            // expected_column_scale
+                       SQL_NULLABLE,                 // 
expected_column_nullability
+                       10,                           // expected_num_prec_radix
+                       8,                            // expected_octet_length
+                       SQL_PRED_NONE,                // expected_searchable
+                       SQL_FALSE);                   // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 2,
+                       std::wstring(L"char_col"),  // expected_column_name
+                       SQL_WVARCHAR,               // expected_data_type
+                       SQL_WVARCHAR,               // expected_concise_type
+                       0,                          // expected_display_size
+                       SQL_FALSE,                  // expected_prec_scale
+                       0,                          // expected_length
+                       std::wstring(L""),          // expected_literal_prefix
+                       std::wstring(L""),          // expected_literal_suffix
+                       0,                          // expected_column_size
+                       0,                          // expected_column_scale
+                       SQL_NULLABLE,               // 
expected_column_nullability
+                       0,                          // expected_num_prec_radix
+                       0,                          // expected_octet_length
+                       SQL_PRED_NONE,              // expected_searchable
+                       SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 3,
+                       std::wstring(L"varbinary_col"),  // expected_column_name
+                       SQL_BINARY,                      // expected_data_type
+                       SQL_BINARY,                      // 
expected_concise_type
+                       0,                               // 
expected_display_size
+                       SQL_FALSE,                       // expected_prec_scale
+                       0,                               // expected_length
+                       std::wstring(L""),               // 
expected_literal_prefix
+                       std::wstring(L""),               // 
expected_literal_suffix
+                       0,                               // expected_column_size
+                       0,                               // 
expected_column_scale
+                       SQL_NULLABLE,                    // 
expected_column_nullability
+                       0,                               // 
expected_num_prec_radix
+                       0,                               // 
expected_octet_length
+                       SQL_PRED_NONE,                   // expected_searchable
+                       SQL_TRUE);                       // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 4,
+                       std::wstring(L"double_col"),  // expected_column_name
+                       SQL_DOUBLE,                   // expected_data_type
+                       SQL_DOUBLE,                   // expected_concise_type
+                       24,                           // expected_display_size
+                       SQL_FALSE,                    // expected_prec_scale
+                       8,                            // expected_length
+                       std::wstring(L""),            // expected_literal_prefix
+                       std::wstring(L""),            // expected_literal_suffix
+                       8,                            // expected_column_size
+                       0,                            // expected_column_scale
+                       SQL_NULLABLE,                 // 
expected_column_nullability
+                       2,                            // expected_num_prec_radix
+                       8,                            // expected_octet_length
+                       SQL_PRED_NONE,                // expected_searchable
+                       SQL_FALSE);                   // 
expected_unsigned_column
+}
+
+TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesAllTypesODBCVer2) {
+  // Tests ODBC 2.0 API SQLColAttributes
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  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));
+  CheckSQLColAttributes(this->stmt, 1,
+                        std::wstring(L"bigint_col"),  // expected_column_name
+                        SQL_BIGINT,                   // expected_data_type
+                        20,                           // expected_display_size
+                        SQL_FALSE,                    // expected_prec_scale
+                        8,                            // expected_length
+                        8,                            // expected_column_size
+                        0,                            // expected_column_scale
+                        SQL_NULLABLE,                 // 
expected_column_nullability
+                        SQL_PRED_NONE,                // expected_searchable
+                        SQL_FALSE);                   // 
expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 2,
+                        std::wstring(L"char_col"),  // expected_column_name
+                        SQL_WVARCHAR,               // expected_data_type
+                        0,                          // expected_display_size
+                        SQL_FALSE,                  // expected_prec_scale
+                        0,                          // expected_length
+                        0,                          // expected_column_size
+                        0,                          // expected_column_scale
+                        SQL_NULLABLE,               // 
expected_column_nullability
+                        SQL_PRED_NONE,              // expected_searchable
+                        SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 3,
+                        std::wstring(L"varbinary_col"),  // 
expected_column_name
+                        SQL_BINARY,                      // expected_data_type
+                        0,                               // 
expected_display_size
+                        SQL_FALSE,                       // expected_prec_scale
+                        0,                               // expected_length
+                        0,                               // 
expected_column_size
+                        0,                               // 
expected_column_scale
+                        SQL_NULLABLE,                    // 
expected_column_nullability
+                        SQL_PRED_NONE,                   // expected_searchable
+                        SQL_TRUE);                       // 
expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 4,
+                        std::wstring(L"double_col"),  // expected_column_name
+                        SQL_DOUBLE,                   // expected_data_type
+                        24,                           // expected_display_size
+                        SQL_FALSE,                    // expected_prec_scale
+                        8,                            // expected_length
+                        8,                            // expected_column_size
+                        0,                            // expected_column_scale
+                        SQL_NULLABLE,                 // 
expected_column_nullability
+                        SQL_PRED_NONE,                // expected_searchable
+                        SQL_FALSE);                   // 
expected_unsigned_column
+}
+
+TEST_F(ColumnsRemoteTest, TestSQLColAttributeAllTypes) {
+  // Test assumes there is a table $scratch.ODBCTest in remote server
+
+  std::wstring wsql = L"SELECT * from $scratch.ODBCTest;";
+  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));
+
+  CheckSQLColAttribute(this->stmt, 1,
+                       std::wstring(L"sinteger_max"),  // expected_column_name
+                       SQL_INTEGER,                    // expected_data_type
+                       SQL_INTEGER,                    // expected_concise_type
+                       11,                             // expected_display_size
+                       SQL_FALSE,                      // expected_prec_scale
+                       4,                              // expected_length
+                       std::wstring(L""),              // 
expected_literal_prefix
+                       std::wstring(L""),              // 
expected_literal_suffix
+                       4,                              // expected_column_size
+                       0,                              // expected_column_scale
+                       SQL_NULLABLE,                   // 
expected_column_nullability
+                       10,                             // 
expected_num_prec_radix
+                       4,                              // expected_octet_length
+                       SQL_SEARCHABLE,                 // expected_searchable
+                       SQL_FALSE);                     // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 2,
+                       std::wstring(L"sbigint_max"),  // expected_column_name
+                       SQL_BIGINT,                    // expected_data_type
+                       SQL_BIGINT,                    // expected_concise_type
+                       20,                            // expected_display_size
+                       SQL_FALSE,                     // expected_prec_scale
+                       8,                             // expected_length
+                       std::wstring(L""),             // 
expected_literal_prefix
+                       std::wstring(L""),             // 
expected_literal_suffix
+                       8,                             // expected_column_size
+                       0,                             // expected_column_scale
+                       SQL_NULLABLE,                  // 
expected_column_nullability
+                       10,                            // 
expected_num_prec_radix
+                       8,                             // expected_octet_length
+                       SQL_SEARCHABLE,                // expected_searchable
+                       SQL_FALSE);                    // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 3,
+                       std::wstring(L"decimal_positive"),  // 
expected_column_name
+                       SQL_DECIMAL,                        // 
expected_data_type
+                       SQL_DECIMAL,                        // 
expected_concise_type
+                       40,                                 // 
expected_display_size
+                       SQL_FALSE,                          // 
expected_prec_scale
+                       19,                                 // expected_length
+                       std::wstring(L""),                  // 
expected_literal_prefix
+                       std::wstring(L""),                  // 
expected_literal_suffix
+                       19,                                 // 
expected_column_size
+                       0,                                  // 
expected_column_scale
+                       SQL_NULLABLE,                       // 
expected_column_nullability
+                       10,                                 // 
expected_num_prec_radix
+                       40,                                 // 
expected_octet_length
+                       SQL_SEARCHABLE,                     // 
expected_searchable
+                       SQL_FALSE);                         // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 4,
+                       std::wstring(L"float_max"),  // expected_column_name
+                       SQL_FLOAT,                   // expected_data_type
+                       SQL_FLOAT,                   // expected_concise_type
+                       24,                          // expected_display_size
+                       SQL_FALSE,                   // expected_prec_scale
+                       8,                           // expected_length
+                       std::wstring(L""),           // expected_literal_prefix
+                       std::wstring(L""),           // expected_literal_suffix
+                       8,                           // expected_column_size
+                       0,                           // expected_column_scale
+                       SQL_NULLABLE,                // 
expected_column_nullability
+                       2,                           // expected_num_prec_radix
+                       8,                           // expected_octet_length
+                       SQL_SEARCHABLE,              // expected_searchable
+                       SQL_FALSE);                  // expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 5,
+                       std::wstring(L"double_max"),  // expected_column_name
+                       SQL_DOUBLE,                   // expected_data_type
+                       SQL_DOUBLE,                   // expected_concise_type
+                       24,                           // expected_display_size
+                       SQL_FALSE,                    // expected_prec_scale
+                       8,                            // expected_length
+                       std::wstring(L""),            // expected_literal_prefix
+                       std::wstring(L""),            // expected_literal_suffix
+                       8,                            // expected_column_size
+                       0,                            // expected_column_scale
+                       SQL_NULLABLE,                 // 
expected_column_nullability
+                       2,                            // expected_num_prec_radix
+                       8,                            // expected_octet_length
+                       SQL_SEARCHABLE,               // expected_searchable
+                       SQL_FALSE);                   // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 6,
+                       std::wstring(L"bit_true"),  // expected_column_name
+                       SQL_BIT,                    // expected_data_type
+                       SQL_BIT,                    // expected_concise_type
+                       1,                          // expected_display_size
+                       SQL_FALSE,                  // expected_prec_scale
+                       1,                          // expected_length
+                       std::wstring(L""),          // expected_literal_prefix
+                       std::wstring(L""),          // expected_literal_suffix
+                       1,                          // expected_column_size
+                       0,                          // expected_column_scale
+                       SQL_NULLABLE,               // 
expected_column_nullability
+                       0,                          // expected_num_prec_radix
+                       1,                          // expected_octet_length
+                       SQL_SEARCHABLE,             // expected_searchable
+                       SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 7,
+                       std::wstring(L"date_max"),  // expected_column_name
+                       SQL_DATETIME,               // expected_data_type
+                       SQL_TYPE_DATE,              // expected_concise_type
+                       10,                         // expected_display_size
+                       SQL_FALSE,                  // expected_prec_scale
+                       10,                         // expected_length
+                       std::wstring(L""),          // expected_literal_prefix
+                       std::wstring(L""),          // expected_literal_suffix
+                       10,                         // expected_column_size
+                       0,                          // expected_column_scale
+                       SQL_NULLABLE,               // 
expected_column_nullability
+                       0,                          // expected_num_prec_radix
+                       6,                          // expected_octet_length
+                       SQL_SEARCHABLE,             // expected_searchable
+                       SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 8,
+                       std::wstring(L"time_max"),  // expected_column_name
+                       SQL_DATETIME,               // expected_data_type
+                       SQL_TYPE_TIME,              // expected_concise_type
+                       12,                         // expected_display_size
+                       SQL_FALSE,                  // expected_prec_scale
+                       12,                         // expected_length
+                       std::wstring(L""),          // expected_literal_prefix
+                       std::wstring(L""),          // expected_literal_suffix
+                       12,                         // expected_column_size
+                       3,                          // expected_column_scale
+                       SQL_NULLABLE,               // 
expected_column_nullability
+                       0,                          // expected_num_prec_radix
+                       6,                          // expected_octet_length
+                       SQL_SEARCHABLE,             // expected_searchable
+                       SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 9,
+                       std::wstring(L"timestamp_max"),  // expected_column_name
+                       SQL_DATETIME,                    // expected_data_type
+                       SQL_TYPE_TIMESTAMP,              // 
expected_concise_type
+                       23,                              // 
expected_display_size
+                       SQL_FALSE,                       // expected_prec_scale
+                       23,                              // expected_length
+                       std::wstring(L""),               // 
expected_literal_prefix
+                       std::wstring(L""),               // 
expected_literal_suffix
+                       23,                              // expected_column_size
+                       3,                               // 
expected_column_scale
+                       SQL_NULLABLE,                    // 
expected_column_nullability
+                       0,                               // 
expected_num_prec_radix
+                       16,                              // 
expected_octet_length
+                       SQL_SEARCHABLE,                  // expected_searchable
+                       SQL_TRUE);                       // 
expected_unsigned_column
+}
+
+TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributeAllTypesODBCVer2) {
+  // Test assumes there is a table $scratch.ODBCTest in remote server
+  std::wstring wsql = L"SELECT * from $scratch.ODBCTest;";
+  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));
+
+  CheckSQLColAttribute(this->stmt, 1,
+                       std::wstring(L"sinteger_max"),  // expected_column_name
+                       SQL_INTEGER,                    // expected_data_type
+                       SQL_INTEGER,                    // expected_concise_type
+                       11,                             // expected_display_size
+                       SQL_FALSE,                      // expected_prec_scale
+                       4,                              // expected_length
+                       std::wstring(L""),              // 
expected_literal_prefix
+                       std::wstring(L""),              // 
expected_literal_suffix
+                       4,                              // expected_column_size
+                       0,                              // expected_column_scale
+                       SQL_NULLABLE,                   // 
expected_column_nullability
+                       10,                             // 
expected_num_prec_radix
+                       4,                              // expected_octet_length
+                       SQL_SEARCHABLE,                 // expected_searchable
+                       SQL_FALSE);                     // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 2,
+                       std::wstring(L"sbigint_max"),  // expected_column_name
+                       SQL_BIGINT,                    // expected_data_type
+                       SQL_BIGINT,                    // expected_concise_type
+                       20,                            // expected_display_size
+                       SQL_FALSE,                     // expected_prec_scale
+                       8,                             // expected_length
+                       std::wstring(L""),             // 
expected_literal_prefix
+                       std::wstring(L""),             // 
expected_literal_suffix
+                       8,                             // expected_column_size
+                       0,                             // expected_column_scale
+                       SQL_NULLABLE,                  // 
expected_column_nullability
+                       10,                            // 
expected_num_prec_radix
+                       8,                             // expected_octet_length
+                       SQL_SEARCHABLE,                // expected_searchable
+                       SQL_FALSE);                    // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 3,
+                       std::wstring(L"decimal_positive"),  // 
expected_column_name
+                       SQL_DECIMAL,                        // 
expected_data_type
+                       SQL_DECIMAL,                        // 
expected_concise_type
+                       40,                                 // 
expected_display_size
+                       SQL_FALSE,                          // 
expected_prec_scale
+                       19,                                 // expected_length
+                       std::wstring(L""),                  // 
expected_literal_prefix
+                       std::wstring(L""),                  // 
expected_literal_suffix
+                       19,                                 // 
expected_column_size
+                       0,                                  // 
expected_column_scale
+                       SQL_NULLABLE,                       // 
expected_column_nullability
+                       10,                                 // 
expected_num_prec_radix
+                       40,                                 // 
expected_octet_length
+                       SQL_SEARCHABLE,                     // 
expected_searchable
+                       SQL_FALSE);                         // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 4,
+                       std::wstring(L"float_max"),  // expected_column_name
+                       SQL_FLOAT,                   // expected_data_type
+                       SQL_FLOAT,                   // expected_concise_type
+                       24,                          // expected_display_size
+                       SQL_FALSE,                   // expected_prec_scale
+                       8,                           // expected_length
+                       std::wstring(L""),           // expected_literal_prefix
+                       std::wstring(L""),           // expected_literal_suffix
+                       8,                           // expected_column_size
+                       0,                           // expected_column_scale
+                       SQL_NULLABLE,                // 
expected_column_nullability
+                       2,                           // expected_num_prec_radix
+                       8,                           // expected_octet_length
+                       SQL_SEARCHABLE,              // expected_searchable
+                       SQL_FALSE);                  // expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 5,
+                       std::wstring(L"double_max"),  // expected_column_name
+                       SQL_DOUBLE,                   // expected_data_type
+                       SQL_DOUBLE,                   // expected_concise_type
+                       24,                           // expected_display_size
+                       SQL_FALSE,                    // expected_prec_scale
+                       8,                            // expected_length
+                       std::wstring(L""),            // expected_literal_prefix
+                       std::wstring(L""),            // expected_literal_suffix
+                       8,                            // expected_column_size
+                       0,                            // expected_column_scale
+                       SQL_NULLABLE,                 // 
expected_column_nullability
+                       2,                            // expected_num_prec_radix
+                       8,                            // expected_octet_length
+                       SQL_SEARCHABLE,               // expected_searchable
+                       SQL_FALSE);                   // 
expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 6,
+                       std::wstring(L"bit_true"),  // expected_column_name
+                       SQL_BIT,                    // expected_data_type
+                       SQL_BIT,                    // expected_concise_type
+                       1,                          // expected_display_size
+                       SQL_FALSE,                  // expected_prec_scale
+                       1,                          // expected_length
+                       std::wstring(L""),          // expected_literal_prefix
+                       std::wstring(L""),          // expected_literal_suffix
+                       1,                          // expected_column_size
+                       0,                          // expected_column_scale
+                       SQL_NULLABLE,               // 
expected_column_nullability
+                       0,                          // expected_num_prec_radix
+                       1,                          // expected_octet_length
+                       SQL_SEARCHABLE,             // expected_searchable
+                       SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 7,
+                       std::wstring(L"date_max"),  // expected_column_name
+                       SQL_DATETIME,               // expected_data_type
+                       SQL_DATE,                   // expected_concise_type
+                       10,                         // expected_display_size
+                       SQL_FALSE,                  // expected_prec_scale
+                       10,                         // expected_length
+                       std::wstring(L""),          // expected_literal_prefix
+                       std::wstring(L""),          // expected_literal_suffix
+                       10,                         // expected_column_size
+                       0,                          // expected_column_scale
+                       SQL_NULLABLE,               // 
expected_column_nullability
+                       0,                          // expected_num_prec_radix
+                       6,                          // expected_octet_length
+                       SQL_SEARCHABLE,             // expected_searchable
+                       SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 8,
+                       std::wstring(L"time_max"),  // expected_column_name
+                       SQL_DATETIME,               // expected_data_type
+                       SQL_TIME,                   // expected_concise_type
+                       12,                         // expected_display_size
+                       SQL_FALSE,                  // expected_prec_scale
+                       12,                         // expected_length
+                       std::wstring(L""),          // expected_literal_prefix
+                       std::wstring(L""),          // expected_literal_suffix
+                       12,                         // expected_column_size
+                       3,                          // expected_column_scale
+                       SQL_NULLABLE,               // 
expected_column_nullability
+                       0,                          // expected_num_prec_radix
+                       6,                          // expected_octet_length
+                       SQL_SEARCHABLE,             // expected_searchable
+                       SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttribute(this->stmt, 9,
+                       std::wstring(L"timestamp_max"),  // expected_column_name
+                       SQL_DATETIME,                    // expected_data_type
+                       SQL_TIMESTAMP,                   // 
expected_concise_type
+                       23,                              // 
expected_display_size
+                       SQL_FALSE,                       // expected_prec_scale
+                       23,                              // expected_length
+                       std::wstring(L""),               // 
expected_literal_prefix
+                       std::wstring(L""),               // 
expected_literal_suffix
+                       23,                              // expected_column_size
+                       3,                               // 
expected_column_scale
+                       SQL_NULLABLE,                    // 
expected_column_nullability
+                       0,                               // 
expected_num_prec_radix
+                       16,                              // 
expected_octet_length
+                       SQL_SEARCHABLE,                  // expected_searchable
+                       SQL_TRUE);                       // 
expected_unsigned_column
+}
+
+TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributesAllTypesODBCVer2) {
+  // Tests ODBC 2.0 API SQLColAttributes
+  // Test assumes there is a table $scratch.ODBCTest in remote server
+  std::wstring wsql = L"SELECT * from $scratch.ODBCTest;";
+  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));
+
+  CheckSQLColAttributes(this->stmt, 1,
+                        std::wstring(L"sinteger_max"),  // expected_column_name
+                        SQL_INTEGER,                    // expected_data_type
+                        11,                             // 
expected_display_size
+                        SQL_FALSE,                      // expected_prec_scale
+                        4,                              // expected_length
+                        4,                              // expected_column_size
+                        0,                              // 
expected_column_scale
+                        SQL_NULLABLE,                   // 
expected_column_nullability
+                        SQL_SEARCHABLE,                 // expected_searchable
+                        SQL_FALSE);                     // 
expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 2,
+                        std::wstring(L"sbigint_max"),  // expected_column_name
+                        SQL_BIGINT,                    // expected_data_type
+                        20,                            // expected_display_size
+                        SQL_FALSE,                     // expected_prec_scale
+                        8,                             // expected_length
+                        8,                             // expected_column_size
+                        0,                             // expected_column_scale
+                        SQL_NULLABLE,                  // 
expected_column_nullability
+                        SQL_SEARCHABLE,                // expected_searchable
+                        SQL_FALSE);                    // 
expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 3,
+                        std::wstring(L"decimal_positive"),  // 
expected_column_name
+                        SQL_DECIMAL,                        // 
expected_data_type
+                        40,                                 // 
expected_display_size
+                        SQL_FALSE,                          // 
expected_prec_scale
+                        19,                                 // expected_length
+                        19,                                 // 
expected_column_size
+                        0,                                  // 
expected_column_scale
+                        SQL_NULLABLE,                       // 
expected_column_nullability
+                        SQL_SEARCHABLE,                     // 
expected_searchable
+                        SQL_FALSE);                         // 
expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 4,
+                        std::wstring(L"float_max"),  // expected_column_name
+                        SQL_FLOAT,                   // expected_data_type
+                        24,                          // expected_display_size
+                        SQL_FALSE,                   // expected_prec_scale
+                        8,                           // expected_length
+                        8,                           // expected_column_size
+                        0,                           // expected_column_scale
+                        SQL_NULLABLE,                // 
expected_column_nullability
+                        SQL_SEARCHABLE,              // expected_searchable
+                        SQL_FALSE);                  // 
expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 5,
+                        std::wstring(L"double_max"),  // expected_column_name
+                        SQL_DOUBLE,                   // expected_data_type
+                        24,                           // expected_display_size
+                        SQL_FALSE,                    // expected_prec_scale
+                        8,                            // expected_length
+                        8,                            // expected_column_size
+                        0,                            // expected_column_scale
+                        SQL_NULLABLE,                 // 
expected_column_nullability
+                        SQL_SEARCHABLE,               // expected_searchable
+                        SQL_FALSE);                   // 
expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 6,
+                        std::wstring(L"bit_true"),  // expected_column_name
+                        SQL_BIT,                    // expected_data_type
+                        1,                          // expected_display_size
+                        SQL_FALSE,                  // expected_prec_scale
+                        1,                          // expected_length
+                        1,                          // expected_column_size
+                        0,                          // expected_column_scale
+                        SQL_NULLABLE,               // 
expected_column_nullability
+                        SQL_SEARCHABLE,             // expected_searchable
+                        SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 7,
+                        std::wstring(L"date_max"),  // expected_column_name
+                        SQL_DATE,                   // expected_data_type
+                        10,                         // expected_display_size
+                        SQL_FALSE,                  // expected_prec_scale
+                        10,                         // expected_length
+                        10,                         // expected_column_size
+                        0,                          // expected_column_scale
+                        SQL_NULLABLE,               // 
expected_column_nullability
+                        SQL_SEARCHABLE,             // expected_searchable
+                        SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 8,
+                        std::wstring(L"time_max"),  // expected_column_name
+                        SQL_TIME,                   // expected_data_type
+                        12,                         // expected_display_size
+                        SQL_FALSE,                  // expected_prec_scale
+                        12,                         // expected_length
+                        12,                         // expected_column_size
+                        3,                          // expected_column_scale
+                        SQL_NULLABLE,               // 
expected_column_nullability
+                        SQL_SEARCHABLE,             // expected_searchable
+                        SQL_TRUE);                  // expected_unsigned_column
+
+  CheckSQLColAttributes(this->stmt, 9,
+                        std::wstring(L"timestamp_max"),  // 
expected_column_name
+                        SQL_TIMESTAMP,                   // expected_data_type
+                        23,                              // 
expected_display_size
+                        SQL_FALSE,                       // expected_prec_scale
+                        23,                              // expected_length
+                        23,                              // 
expected_column_size
+                        3,                               // 
expected_column_scale
+                        SQL_NULLABLE,                    // 
expected_column_nullability
+                        SQL_SEARCHABLE,                  // expected_searchable
+                        SQL_TRUE);                       // 
expected_unsigned_column
+}
+
+TYPED_TEST(ColumnsTest, TestSQLColAttributeCaseSensitive) {
+  // Arrow limitation: returns SQL_FALSE for case sensitive column
+
+  std::wstring wsql = this->GetQueryAllDataTypes();
+  // Int column
+  SQLLEN value;
+  GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_CASE_SENSITIVE, 
&value);
+  ASSERT_EQ(SQL_FALSE, value);
+  SQLFreeStmt(this->stmt, SQL_CLOSE);
+  // Varchar column
+  GetSQLColAttributeNumeric(this->stmt, wsql, 28, SQL_DESC_CASE_SENSITIVE, 
&value);
+  ASSERT_EQ(SQL_FALSE, value);
+}
+
+TYPED_TEST(ColumnsOdbcV2Test, TestSQLColAttributesCaseSensitive) {
+  // Arrow limitation: returns SQL_FALSE for case sensitive column
+  // Tests ODBC 2.0 API SQLColAttributes
+
+  std::wstring wsql = this->GetQueryAllDataTypes();
+  // Int column
+  SQLLEN value;
+  GetSQLColAttributesNumeric(this->stmt, wsql, 1, SQL_COLUMN_CASE_SENSITIVE, 
&value);
+  ASSERT_EQ(SQL_FALSE, value);
+  SQLFreeStmt(this->stmt, SQL_CLOSE);
+  // Varchar column
+  GetSQLColAttributesNumeric(this->stmt, wsql, 28, SQL_COLUMN_CASE_SENSITIVE, 
&value);
+  ASSERT_EQ(SQL_FALSE, value);
+}
+
+TEST_F(ColumnsMockTest, TestSQLColAttributeUniqueValue) {
+  // Mock server limitation: returns false for auto-increment column
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  SQLLEN value;
+  GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_AUTO_UNIQUE_VALUE, 
&value);
+  ASSERT_EQ(SQL_FALSE, value);
+}
+
+TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesAutoIncrement) {
+  // Tests ODBC 2.0 API SQLColAttributes
+  // Mock server limitation: returns false for auto-increment column
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  SQLLEN value;
+  GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_COLUMN_AUTO_INCREMENT, 
&value);
+  ASSERT_EQ(SQL_FALSE, value);
+}
+
+TEST_F(ColumnsMockTest, TestSQLColAttributeBaseTableName) {
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_BASE_TABLE_NAME, 
value);
+  ASSERT_EQ(std::wstring(L"AllTypesTable"), value);
+}
+
+TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesTableName) {
+  // Tests ODBC 2.0 API SQLColAttributes
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  std::wstring value;
+  GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TABLE_NAME, value);
+  ASSERT_EQ(std::wstring(L"AllTypesTable"), value);
+}
+
+TEST_F(ColumnsMockTest, TestSQLColAttributeCatalogName) {
+  // Mock server limitattion: mock doesn't return catalog for result metadata,
+  // and the defautl catalog should be 'main'
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_CATALOG_NAME, value);
+  ASSERT_EQ(std::wstring(L""), value);
+}
+
+TEST_F(ColumnsRemoteTest, TestSQLColAttributeCatalogName) {
+  // Remote server does not have catalogs
+
+  std::wstring wsql = L"SELECT * from $scratch.ODBCTest;";
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_CATALOG_NAME, value);
+  ASSERT_EQ(std::wstring(L""), value);
+}
+
+TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesQualifierName) {
+  // Mock server limitattion: mock doesn't return catalog for result metadata,
+  // and the defautl catalog should be 'main'
+  // Tests ODBC 2.0 API SQLColAttributes
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_COLUMN_QUALIFIER_NAME, 
value);
+  ASSERT_EQ(std::wstring(L""), value);
+}
+
+TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributesQualifierName) {
+  // Remote server does not have catalogs
+  // Tests ODBC 2.0 API SQLColAttributes
+  std::wstring wsql = L"SELECT * from $scratch.ODBCTest;";
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_COLUMN_QUALIFIER_NAME, 
value);
+  ASSERT_EQ(std::wstring(L""), value);
+}
+
+TYPED_TEST(ColumnsTest, TestSQLColAttributeCount) {
+  std::wstring wsql = this->GetQueryAllDataTypes();
+  // Pass 0 as column number, driver should ignore it
+  SQLLEN value;
+  GetSQLColAttributeNumeric(this->stmt, wsql, 0, SQL_DESC_COUNT, &value);
+  ASSERT_EQ(32, value);
+}
+
+TEST_F(ColumnsMockTest, TestSQLColAttributeLocalTypeName) {
+  std::wstring wsql = this->GetQueryAllDataTypes();
+  // Mock server doesn't have local type name
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_LOCAL_TYPE_NAME, 
value);
+  ASSERT_EQ(std::wstring(L""), value);
+}
+
+TEST_F(ColumnsRemoteTest, TestSQLColAttributeLocalTypeName) {
+  std::wstring wsql = this->GetQueryAllDataTypes();
+  std::wstring value;
+  GetSQLColAttributesString(this->stmt, wsql, 1, SQL_DESC_LOCAL_TYPE_NAME, 
value);
+  ASSERT_EQ(std::wstring(L"INTEGER"), value);
+}
+
+TEST_F(ColumnsMockTest, TestSQLColAttributeSchemaName) {
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  // Mock server doesn't have schemas
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_SCHEMA_NAME, value);
+  ASSERT_EQ(std::wstring(L""), value);
+}
+
+TEST_F(ColumnsRemoteTest, TestSQLColAttributeSchemaName) {
+  // Test assumes there is a table $scratch.ODBCTest in remote server
+
+  std::wstring wsql = L"SELECT * from $scratch.ODBCTest;";
+  // Remote server limitation: doesn't return schema name, expected schema 
name is
+  // $scratch
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_SCHEMA_NAME, value);
+  ASSERT_EQ(std::wstring(L""), value);
+}
+
+TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesOwnerName) {
+  // Tests ODBC 2.0 API SQLColAttributes
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  // Mock server doesn't have schemas
+  std::wstring value;
+  GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_OWNER_NAME, value);
+  ASSERT_EQ(std::wstring(L""), value);
+}
+
+TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributesOwnerName) {
+  // Test assumes there is a table $scratch.ODBCTest in remote server
+  // Tests ODBC 2.0 API SQLColAttributes
+  std::wstring wsql = L"SELECT * from $scratch.ODBCTest;";
+  // Remote server limitation: doesn't return schema name, expected schema 
name is
+  // $scratch
+  std::wstring value;
+  GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_OWNER_NAME, value);
+  ASSERT_EQ(std::wstring(L""), value);
+}
+
+TEST_F(ColumnsMockTest, TestSQLColAttributeTableName) {
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TABLE_NAME, value);
+  ASSERT_EQ(std::wstring(L"AllTypesTable"), value);
+}
+
+TEST_F(ColumnsMockTest, TestSQLColAttributeTypeName) {
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"BIGINT"), value);
+  GetSQLColAttributeString(this->stmt, L"", 2, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"WVARCHAR"), value);
+  GetSQLColAttributeString(this->stmt, L"", 3, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"BINARY"), value);
+  GetSQLColAttributeString(this->stmt, L"", 4, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"DOUBLE"), value);
+}
+
+TEST_F(ColumnsRemoteTest, TestSQLColAttributeTypeName) {
+  std::wstring wsql = L"SELECT * from $scratch.ODBCTest;";
+  std::wstring value;
+  GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"INTEGER"), value);
+  GetSQLColAttributeString(this->stmt, L"", 2, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"BIGINT"), value);
+  GetSQLColAttributeString(this->stmt, L"", 3, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"DECIMAL"), value);
+  GetSQLColAttributeString(this->stmt, L"", 4, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"FLOAT"), value);
+  GetSQLColAttributeString(this->stmt, L"", 5, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"DOUBLE"), value);
+  GetSQLColAttributeString(this->stmt, L"", 6, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"BOOLEAN"), value);
+  GetSQLColAttributeString(this->stmt, L"", 7, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"DATE"), value);
+  GetSQLColAttributeString(this->stmt, L"", 8, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"TIME"), value);
+  GetSQLColAttributeString(this->stmt, L"", 9, SQL_DESC_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"TIMESTAMP"), value);
+}
+
+TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesTypeName) {
+  // Tests ODBC 2.0 API SQLColAttributes
+  this->CreateTableAllDataType();
+
+  std::wstring wsql = L"SELECT * from AllTypesTable;";
+  // Mock server doesn't return data source-dependent data type name
+  std::wstring value;
+  GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"BIGINT"), value);
+  GetSQLColAttributesString(this->stmt, L"", 2, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"WVARCHAR"), value);
+  GetSQLColAttributesString(this->stmt, L"", 3, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"BINARY"), value);
+  GetSQLColAttributesString(this->stmt, L"", 4, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"DOUBLE"), value);
+}
+
+TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributesTypeName) {
+  // Tests ODBC 2.0 API SQLColAttributes
+  std::wstring wsql = L"SELECT * from $scratch.ODBCTest;";
+  std::wstring value;
+  GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"INTEGER"), value);
+  GetSQLColAttributesString(this->stmt, L"", 2, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"BIGINT"), value);
+  GetSQLColAttributesString(this->stmt, L"", 3, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"DECIMAL"), value);
+  GetSQLColAttributesString(this->stmt, L"", 4, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"FLOAT"), value);
+  GetSQLColAttributesString(this->stmt, L"", 5, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"DOUBLE"), value);
+  GetSQLColAttributesString(this->stmt, L"", 6, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"BOOLEAN"), value);
+  GetSQLColAttributesString(this->stmt, L"", 7, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"DATE"), value);
+  GetSQLColAttributesString(this->stmt, L"", 8, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"TIME"), value);
+  GetSQLColAttributesString(this->stmt, L"", 9, SQL_COLUMN_TYPE_NAME, value);
+  ASSERT_EQ(std::wstring(L"TIMESTAMP"), value);
+}
+
+TYPED_TEST(ColumnsTest, TestSQLColAttributeUnnamed) {
+  std::wstring wsql = this->GetQueryAllDataTypes();
+  SQLLEN value;
+  GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_UNNAMED, &value);
+  ASSERT_EQ(SQL_NAMED, value);
+}
+
+TYPED_TEST(ColumnsTest, TestSQLColAttributeUpdatable) {
+  std::wstring wsql = this->GetQueryAllDataTypes();
+  // Mock server and remote server do not return updatable information
+  SQLLEN value;
+  GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_UPDATABLE, &value);
+  ASSERT_EQ(SQL_ATTR_READWRITE_UNKNOWN, value);
+}
+
+TYPED_TEST(ColumnsOdbcV2Test, TestSQLColAttributesUpdatable) {
+  // Tests ODBC 2.0 API SQLColAttributes
+  std::wstring wsql = this->GetQueryAllDataTypes();
+  // Mock server and remote server do not return updatable information
+  SQLLEN value;
+  GetSQLColAttributesNumeric(this->stmt, wsql, 1, SQL_COLUMN_UPDATABLE, 
&value);
+  ASSERT_EQ(SQL_ATTR_READWRITE_UNKNOWN, value);
+}
+
 }  // namespace arrow::flight::sql::odbc

Reply via email to