Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions cpp/src/arrow/flight/sql/odbc/entry_points.cc
Original file line number Diff line number Diff line change
Expand Up @@ -179,18 +179,14 @@ SQLRETURN SQL_API SQLCloseCursor(SQLHSTMT stmt) {
return SQL_ERROR;
}

SQLRETURN SQL_API SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT columnNumber,
SQLRETURN SQL_API SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT recordNumber,
SQLUSMALLINT fieldIdentifier,
SQLPOINTER characterAttributePtr,
SQLSMALLINT bufferLength, SQLSMALLINT* stringLengthPtr,
SQLSMALLINT bufferLength, SQLSMALLINT* outputLength,
SQLLEN* numericAttributePtr) {
LOG_DEBUG(
"SQLColAttributeW called with stmt: {}, columnNumber: {}, "
"fieldIdentifier: {}, characterAttributePtr: {}, bufferLength: {}, "
"stringLengthPtr: {}, numericAttributePtr: {}",
stmt, columnNumber, fieldIdentifier, characterAttributePtr, bufferLength,
fmt::ptr(stringLengthPtr), fmt::ptr(numericAttributePtr));
return SQL_ERROR;
return arrow::SQLColAttribute(stmt, recordNumber, fieldIdentifier,
characterAttributePtr, bufferLength, outputLength,
numericAttributePtr);
}

SQLRETURN SQL_API SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,18 +260,29 @@ bool FlightSqlResultSetMetadata::IsUnsigned(int column_position) {
const std::shared_ptr<Field>& field = schema_->field(column_position - 1);

switch (field->type()->id()) {
case arrow::Type::INT8:
case arrow::Type::INT16:
case arrow::Type::INT32:
case arrow::Type::INT64:
case arrow::Type::DOUBLE:
case arrow::Type::FLOAT:
case arrow::Type::HALF_FLOAT:
case arrow::Type::DECIMAL32:
case arrow::Type::DECIMAL64:
case arrow::Type::DECIMAL128:
case arrow::Type::DECIMAL256:
return false;
case arrow::Type::UINT8:
case arrow::Type::UINT16:
case arrow::Type::UINT32:
case arrow::Type::UINT64:
return true;
default:
return false;
return true;
}
}

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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class FlightSqlResultSetMetadata : public odbcabstraction::ResultSetMetadata {

odbcabstraction::Searchability IsSearchable(int column_position) override;

/// \brief Returns true if the column is unsigned (not numeric)
bool IsUnsigned(int column_position) override;

bool IsFixedPrecScale(int column_position) override;
Expand Down
95 changes: 95 additions & 0 deletions cpp/src/arrow/flight/sql/odbc/odbc_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1147,4 +1147,99 @@ SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNa
return SQL_SUCCESS;
});
}

SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT recordNumber,
SQLUSMALLINT fieldIdentifier, SQLPOINTER characterAttributePtr,
SQLSMALLINT bufferLength, SQLSMALLINT* outputLength,
SQLLEN* numericAttributePtr) {
LOG_DEBUG(
"SQLColAttributeW called with stmt: {}, recordNumber: {}, "
"fieldIdentifier: {}, characterAttributePtr: {}, bufferLength: {}, "
"outputLength: {}, numericAttributePtr: {}",
stmt, recordNumber, fieldIdentifier, characterAttributePtr, bufferLength,
fmt::ptr(outputLength), fmt::ptr(numericAttributePtr));
using ODBC::ODBCDescriptor;
using ODBC::ODBCStatement;
return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
ODBCDescriptor* ird = statement->GetIRD();
SQLINTEGER outputLengthInt;
switch (fieldIdentifier) {

@jduo jduo Jul 23, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQLColAttribute needs to handle the special SQL_COLUMN_* identifiers. I think these were for ODBC 2.x. Please test these as well.

@alinaliBQ alinaliBQ Jul 23, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. I have added implementation for SQL_COLUMN_LENGTH, SQL_COLUMN_PRECISION, and SQL_COLUMN_SCALE, and made them return the same values as their corresponding SQL_DESC_* identifiers. For the rest of the SQL_COLUMN_* identifiers, they are the same values as the corresponding SQL_DESC_* identifiers, and the driver manager will map them accordingly. I have added ODBC Ver2 tests with SQLColAttributes that pass SQL_COLUMN_* identifiers as arguments.

// Numeric attributes
// internal is SQLLEN, no conversion is needed
case SQL_DESC_DISPLAY_SIZE:
case SQL_DESC_OCTET_LENGTH: {
ird->GetField(recordNumber, fieldIdentifier, numericAttributePtr, bufferLength,
&outputLengthInt);
break;
}
// internal is SQLULEN, conversion is needed.
case SQL_COLUMN_LENGTH: // ODBC 2.0
case SQL_DESC_LENGTH: {
SQLULEN temp;
ird->GetField(recordNumber, fieldIdentifier, &temp, bufferLength,
&outputLengthInt);
if (numericAttributePtr) {
*numericAttributePtr = 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(recordNumber, fieldIdentifier, &temp, bufferLength,
&outputLengthInt);
if (numericAttributePtr) {
*numericAttributePtr = 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(recordNumber, fieldIdentifier, &temp, bufferLength,
&outputLengthInt);
if (numericAttributePtr) {
*numericAttributePtr = 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(recordNumber, fieldIdentifier, characterAttributePtr, bufferLength,
&outputLengthInt);
break;
default:
throw DriverException("Invalid descriptor field", "HY091");
}
if (outputLength) {
*outputLength = static_cast<SQLSMALLINT>(outputLengthInt);
}
return SQL_SUCCESS;
});
}
} // namespace arrow
4 changes: 4 additions & 0 deletions cpp/src/arrow/flight/sql/odbc/odbc_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,8 @@ SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNa
SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength,
SQLWCHAR* tableName, SQLSMALLINT tableNameLength,
SQLWCHAR* columnName, SQLSMALLINT columnNameLength);
SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT recordNumber,
SQLUSMALLINT fieldIdentifier, SQLPOINTER characterAttributePtr,
SQLSMALLINT bufferLength, SQLSMALLINT* outputLength,
SQLLEN* numericAttributePtr);
} // namespace arrow
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,9 @@ void ODBCDescriptor::GetHeaderField(SQLSMALLINT fieldIdentifier, SQLPOINTER valu
GetAttribute(m_rowsProccessedPtr, value, bufferLength, outputLength);
break;
case SQL_DESC_COUNT: {
GetAttribute(m_highestOneBasedBoundRecord, value, bufferLength, outputLength);
// m_highestOneBasedBoundRecord equals number of records + 1
GetAttribute(static_cast<SQLSMALLINT>(m_highestOneBasedBoundRecord - 1), value,
bufferLength, outputLength);
break;
}
default:
Expand Down Expand Up @@ -311,52 +313,53 @@ void ODBCDescriptor::GetField(SQLSMALLINT recordNumber, SQLSMALLINT fieldIdentif

// TODO: Restrict fields based on AppDescriptor IPD, and IRD.

bool lengthInBytes = true;
SQLSMALLINT zeroBasedRecord = recordNumber - 1;
const DescriptorRecord& record = m_records[zeroBasedRecord];
switch (fieldIdentifier) {
case SQL_DESC_BASE_COLUMN_NAME:
GetAttributeUTF8(record.m_baseColumnName, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_baseColumnName, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_BASE_TABLE_NAME:
GetAttributeUTF8(record.m_baseTableName, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_baseTableName, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_CATALOG_NAME:
GetAttributeUTF8(record.m_catalogName, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_catalogName, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_LABEL:
GetAttributeUTF8(record.m_label, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_label, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_LITERAL_PREFIX:
GetAttributeUTF8(record.m_literalPrefix, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_literalPrefix, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_LITERAL_SUFFIX:
GetAttributeUTF8(record.m_literalSuffix, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_literalSuffix, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_LOCAL_TYPE_NAME:
GetAttributeUTF8(record.m_localTypeName, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_localTypeName, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_NAME:
GetAttributeUTF8(record.m_name, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_name, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_SCHEMA_NAME:
GetAttributeUTF8(record.m_schemaName, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_schemaName, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_TABLE_NAME:
GetAttributeUTF8(record.m_tableName, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_tableName, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;
case SQL_DESC_TYPE_NAME:
GetAttributeUTF8(record.m_typeName, value, bufferLength, outputLength,
GetDiagnostics());
GetAttributeSQLWCHAR(record.m_typeName, lengthInBytes, value, bufferLength,
outputLength, GetDiagnostics());
break;

case SQL_DESC_DATA_PTR:
Expand All @@ -366,7 +369,7 @@ void ODBCDescriptor::GetField(SQLSMALLINT recordNumber, SQLSMALLINT fieldIdentif
case SQL_DESC_OCTET_LENGTH_PTR:
GetAttribute(record.m_indicatorPtr, value, bufferLength, outputLength);
break;

case SQL_COLUMN_LENGTH: // ODBC 2.0
case SQL_DESC_LENGTH:
GetAttribute(record.m_length, value, bufferLength, outputLength);
break;
Expand Down Expand Up @@ -405,12 +408,14 @@ void ODBCDescriptor::GetField(SQLSMALLINT recordNumber, SQLSMALLINT fieldIdentif
case SQL_DESC_PARAMETER_TYPE:
GetAttribute(record.m_paramType, value, bufferLength, outputLength);
break;
case SQL_COLUMN_PRECISION: // ODBC 2.0
case SQL_DESC_PRECISION:
GetAttribute(record.m_precision, value, bufferLength, outputLength);
break;
case SQL_DESC_ROWVER:
GetAttribute(record.m_rowVer, value, bufferLength, outputLength);
break;
case SQL_COLUMN_SCALE: // ODBC 2.0
case SQL_DESC_SCALE:
GetAttribute(record.m_scale, value, bufferLength, outputLength);
break;
Expand Down Expand Up @@ -500,7 +505,8 @@ void ODBCDescriptor::PopulateFromResultSetMetadata(ResultSetMetadata* rsmd) {
m_records[i].m_caseSensitive =
rsmd->IsCaseSensitive(oneBasedIndex) ? SQL_TRUE : SQL_FALSE;
m_records[i].m_datetimeIntervalPrecision; // TODO - update when rsmd adds this
m_records[i].m_numPrecRadix = rsmd->GetNumPrecRadix(oneBasedIndex);
SQLINTEGER numPrecRadix = rsmd->GetNumPrecRadix(oneBasedIndex);
m_records[i].m_numPrecRadix = numPrecRadix > 0 ? numPrecRadix : 0;
m_records[i].m_datetimeIntervalCode; // TODO
m_records[i].m_fixedPrecScale =
rsmd->IsFixedPrecScale(oneBasedIndex) ? SQL_TRUE : SQL_FALSE;
Expand All @@ -510,8 +516,7 @@ void ODBCDescriptor::PopulateFromResultSetMetadata(ResultSetMetadata* rsmd) {
m_records[i].m_rowVer = SQL_FALSE;
m_records[i].m_scale = rsmd->GetScale(oneBasedIndex);
m_records[i].m_searchable = rsmd->IsSearchable(oneBasedIndex);
m_records[i].m_type =
GetSqlTypeForODBCVersion(rsmd->GetDataType(oneBasedIndex), m_is2xConnection);
m_records[i].m_type = rsmd->GetDataType(oneBasedIndex);
m_records[i].m_unnamed = m_records[i].m_name.empty() ? SQL_TRUE : SQL_FALSE;
m_records[i].m_unsigned = rsmd->IsUnsigned(oneBasedIndex) ? SQL_TRUE : SQL_FALSE;
m_records[i].m_updatable = rsmd->GetUpdatable(oneBasedIndex);
Expand Down
Loading
Loading