From be5b158ac39b8bc4cd642482c265c9d6b25a4bc2 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 28 Nov 2025 14:41:15 -0800 Subject: [PATCH 1/2] Extract SQLColAttribute implementation Use nullptr explicitly instead of 0 Co-Authored-By: alinalibq Co-Authored-By: justing-bq --- cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 86 +- .../flight_sql_result_set_metadata.cc | 32 +- .../flight_sql_result_set_metadata.h | 3 +- .../sql/odbc/odbc_impl/odbc_descriptor.cc | 62 +- .../odbc/odbc_impl/spi/result_set_metadata.h | 6 +- .../flight/sql/odbc/tests/columns_test.cc | 1224 +++++++++++++++++ 6 files changed, 1361 insertions(+), 52 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index 50ea395ec4f..312515d8ab3 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -1272,8 +1272,90 @@ SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT record_number, << ", output_length: " << static_cast(output_length) << ", numeric_attribute_ptr: " << static_cast(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(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(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(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(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(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 8ac3c7ed752..11f6c60080f 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 #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 empty_metadata_map(new KeyValueMetadata); - inline ColumnMetadata GetMetadata(const std::shared_ptr& 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 = 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 0d141a4bb9c..b502494f711 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 d2b7f8865ca..eae90ac2b77 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(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 38f81fc9c3e..63da402db7c 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 +#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 81e97a09284..87e9193b7c2 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 name(kOdbcBufferSize); + SQLSMALLINT name_len = 0; + std::vector base_column_name(kOdbcBufferSize); + SQLSMALLINT column_name_len = 0; + std::vector label(kOdbcBufferSize); + SQLSMALLINT label_len = 0; + std::vector prefix(kOdbcBufferSize); + SQLSMALLINT prefix_len = 0; + std::vector 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 name(kOdbcBufferSize); + SQLSMALLINT name_len = 0; + std::vector 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 sql0(wsql.begin(), wsql.end()); + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt)); + } + + // check SQLColAttribute string attribute + std::vector 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 sql0(wsql.begin(), wsql.end()); + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt)); + } + + // check SQLColAttribute string attribute + std::vector 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 sql0(wsql.begin(), wsql.end()); + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(stmt, &sql0[0], static_cast(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 sql0(wsql.begin(), wsql.end()); + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(stmt, &sql0[0], static_cast(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 sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + SQLUSMALLINT idx = 1; + std::vector 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 sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(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 sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + SQLUSMALLINT invalid_field_id = -100; + SQLUSMALLINT idx = 1; + std::vector 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 sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + SQLUSMALLINT invalid_col_id = 2; + std::vector 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 sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(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 sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(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 sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(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 sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(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 sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(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 From 05e08e6233b9a14cbbc0472c4f1e4d52c222651d Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 15 Dec 2025 15:08:45 -0800 Subject: [PATCH 2/2] Fix segfault issue from empty metadata `metadata_map_` would be nullptr if `metadata_map` is null. Downstream might assume `metadata_map_` is always non-null, so it is safer to create an empty map if `metadata_map` is null. --- cpp/src/arrow/flight/sql/column_metadata.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp/src/arrow/flight/sql/column_metadata.cc b/cpp/src/arrow/flight/sql/column_metadata.cc index 30f557084b2..6501e3c7166 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 metadata_map) - : metadata_map_(std::move(metadata_map)) {} + std::shared_ptr metadata_map) { + metadata_map_ = std::move(metadata_map ? metadata_map + : std::make_shared()); +} arrow::Result ColumnMetadata::GetCatalogName() const { return metadata_map_->Get(kCatalogName);