diff --git a/cpp/src/arrow/flight/sql/odbc/entry_points.cc b/cpp/src/arrow/flight/sql/odbc/entry_points.cc index 4b9960f4489f..9aacc844e0fa 100644 --- a/cpp/src/arrow/flight/sql/odbc/entry_points.cc +++ b/cpp/src/arrow/flight/sql/odbc/entry_points.cc @@ -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, diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.cc index 035390981c84..710f7608ecfc 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.cc @@ -260,18 +260,29 @@ bool FlightSqlResultSetMetadata::IsUnsigned(int column_position) { const std::shared_ptr& 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; } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.h index f8e78eb2d6de..29901652c521 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.h @@ -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; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index 5553db102761..69c6059d1d08 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -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(stmt); + ODBCDescriptor* ird = statement->GetIRD(); + SQLINTEGER outputLengthInt; + switch (fieldIdentifier) { + // 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(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(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(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(outputLengthInt); + } + return SQL_SUCCESS; + }); +} } // namespace arrow diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.h b/cpp/src/arrow/flight/sql/odbc/odbc_api.h index b1b5c5a30335..dec6603201dc 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.h @@ -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 diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_descriptor.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_descriptor.cc index b578bea36095..97b31bb550e6 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_descriptor.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_descriptor.cc @@ -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(m_highestOneBasedBoundRecord - 1), value, + bufferLength, outputLength); break; } default: @@ -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: @@ -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; @@ -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; @@ -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; @@ -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); 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 a05b8cd0f3a8..8874572ec63c 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -30,6 +30,7 @@ // ODBC 3. namespace arrow::flight::sql::odbc { +// Helper functions void checkSQLColumns( SQLHSTMT stmt, const std::wstring& expectedTable, const std::wstring& expectedColumn, const SQLINTEGER& expectedDataType, const std::wstring& expectedTypeName, @@ -102,6 +103,269 @@ void checkRemoteSQLColumns( expectedIsNullable); } +void checkSQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT idx, + const std::wstring& expectedColmnName, SQLLEN expectedDataType, + SQLLEN expectedConciseType, SQLLEN expectedDisplaySize, + SQLLEN expectedPrecScale, SQLLEN expectedLength, + const std::wstring& expectedLiteralPrefix, + const std::wstring& expectedLiteralSuffix, + SQLLEN expectedColumnSize, SQLLEN expectedColumnScale, + SQLLEN expectedColumnNullability, SQLLEN expectedNumPrecRadix, + SQLLEN expectedOctetLength, SQLLEN expectedSearchable, + SQLLEN expectedUnsignedColumn) { + std::vector name(ODBC_BUFFER_SIZE); + SQLSMALLINT nameLen = 0; + std::vector baseColumnName(ODBC_BUFFER_SIZE); + SQLSMALLINT columnNameLen = 0; + std::vector label(ODBC_BUFFER_SIZE); + SQLSMALLINT labelLen = 0; + std::vector prefix(ODBC_BUFFER_SIZE); + SQLSMALLINT prefixLen = 0; + std::vector suffix(ODBC_BUFFER_SIZE); + SQLSMALLINT suffixLen = 0; + SQLLEN dataType = 0; + SQLLEN conciseType = 0; + SQLLEN displaySize = 0; + SQLLEN precScale = 0; + SQLLEN length = 0; + SQLLEN size = 0; + SQLLEN scale = 0; + SQLLEN nullability = 0; + SQLLEN numPrecRadix = 0; + SQLLEN octetLength = 0; + SQLLEN searchable = 0; + SQLLEN unsignedCol = 0; + + SQLRETURN ret = SQLColAttribute(stmt, idx, SQL_DESC_NAME, &name[0], + (SQLSMALLINT)name.size(), &nameLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_BASE_COLUMN_NAME, &baseColumnName[0], + (SQLSMALLINT)baseColumnName.size(), &columnNameLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_LABEL, &label[0], (SQLSMALLINT)label.size(), + &labelLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_TYPE, 0, 0, 0, &dataType); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_CONCISE_TYPE, 0, 0, 0, &conciseType); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_DISPLAY_SIZE, 0, 0, 0, &displaySize); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_FIXED_PREC_SCALE, 0, 0, 0, &precScale); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_LENGTH, 0, 0, 0, &length); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_LITERAL_PREFIX, &prefix[0], + (SQLSMALLINT)prefix.size(), &prefixLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_LITERAL_SUFFIX, &suffix[0], + (SQLSMALLINT)suffix.size(), &suffixLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_PRECISION, 0, 0, 0, &size); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_SCALE, 0, 0, 0, &scale); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_NULLABLE, 0, 0, 0, &nullability); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_NUM_PREC_RADIX, 0, 0, 0, &numPrecRadix); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_OCTET_LENGTH, 0, 0, 0, &octetLength); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_SEARCHABLE, 0, 0, 0, &searchable); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_UNSIGNED, 0, 0, 0, &unsignedCol); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring nameStr = ConvertToWString(name, nameLen); + std::wstring baseColumnNameStr = ConvertToWString(baseColumnName, columnNameLen); + std::wstring labelStr = ConvertToWString(label, labelLen); + std::wstring prefixStr = ConvertToWString(prefix, prefixLen); + + // Assume column name, base column name, and label are equivalent in the result set + EXPECT_EQ(nameStr, expectedColmnName); + EXPECT_EQ(baseColumnNameStr, expectedColmnName); + EXPECT_EQ(labelStr, expectedColmnName); + EXPECT_EQ(dataType, expectedDataType); + EXPECT_EQ(conciseType, expectedConciseType); + EXPECT_EQ(displaySize, expectedDisplaySize); + EXPECT_EQ(precScale, expectedPrecScale); + EXPECT_EQ(length, expectedLength); + EXPECT_EQ(prefixStr, expectedLiteralPrefix); + EXPECT_EQ(size, expectedColumnSize); + EXPECT_EQ(scale, expectedColumnScale); + EXPECT_EQ(nullability, expectedColumnNullability); + EXPECT_EQ(numPrecRadix, expectedNumPrecRadix); + EXPECT_EQ(octetLength, expectedOctetLength); + EXPECT_EQ(searchable, expectedSearchable); + EXPECT_EQ(unsignedCol, expectedUnsignedColumn); +} + +void checkSQLColAttributes(SQLHSTMT stmt, SQLUSMALLINT idx, + const std::wstring& expectedColmnName, SQLLEN expectedDataType, + SQLLEN expectedDisplaySize, SQLLEN expectedPrecScale, + SQLLEN expectedLength, SQLLEN expectedColumnSize, + SQLLEN expectedColumnScale, SQLLEN expectedColumnNullability, + SQLLEN expectedSearchable, SQLLEN expectedUnsignedColumn) { + std::vector name(ODBC_BUFFER_SIZE); + SQLSMALLINT nameLen = 0; + std::vector label(ODBC_BUFFER_SIZE); + SQLSMALLINT labelLen = 0; + SQLLEN dataType = 0; + SQLLEN displaySize = 0; + SQLLEN precScale = 0; + SQLLEN length = 0; + SQLLEN size = 0; + SQLLEN scale = 0; + SQLLEN nullability = 0; + SQLLEN searchable = 0; + SQLLEN unsignedCol = 0; + + SQLRETURN ret = SQLColAttributes(stmt, idx, SQL_COLUMN_NAME, &name[0], + (SQLSMALLINT)name.size(), &nameLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_LABEL, &label[0], + (SQLSMALLINT)label.size(), &labelLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_TYPE, 0, 0, 0, &dataType); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_DISPLAY_SIZE, 0, 0, 0, &displaySize); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_COLUMN_MONEY, 0, 0, 0, &precScale); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_LENGTH, 0, 0, 0, &length); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_PRECISION, 0, 0, 0, &size); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_SCALE, 0, 0, 0, &scale); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_NULLABLE, 0, 0, 0, &nullability); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_SEARCHABLE, 0, 0, 0, &searchable); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_UNSIGNED, 0, 0, 0, &unsignedCol); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring nameStr = ConvertToWString(name, nameLen); + std::wstring labelStr = ConvertToWString(label, labelLen); + + EXPECT_EQ(nameStr, expectedColmnName); + EXPECT_EQ(labelStr, expectedColmnName); + EXPECT_EQ(dataType, expectedDataType); + EXPECT_EQ(displaySize, expectedDisplaySize); + EXPECT_EQ(length, expectedLength); + EXPECT_EQ(size, expectedColumnSize); + EXPECT_EQ(scale, expectedColumnScale); + EXPECT_EQ(nullability, expectedColumnNullability); + EXPECT_EQ(searchable, expectedSearchable); + EXPECT_EQ(unsignedCol, expectedUnsignedColumn); +} + +void checkSQLColAttributeString(SQLHSTMT stmt, const std::wstring& wsql, SQLUSMALLINT idx, + SQLUSMALLINT fieldIdentifier, + const std::wstring& expectedAttrString) { + // Execute query and check SQLColAttribute string attribute + std::vector sql0(wsql.begin(), wsql.end()); + SQLRETURN ret = SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::vector strVal(ODBC_BUFFER_SIZE); + SQLSMALLINT strLen = 0; + + ret = SQLColAttribute(stmt, idx, fieldIdentifier, &strVal[0], + (SQLSMALLINT)strVal.size(), &strLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring attrStr = ConvertToWString(strVal, strLen); + EXPECT_EQ(attrStr, expectedAttrString); +} + +void checkSQLColAttributeNumeric(SQLHSTMT stmt, const std::wstring& wsql, + SQLUSMALLINT idx, SQLUSMALLINT fieldIdentifier, + SQLLEN expectedAttrNumeric) { + // Execute query and check SQLColAttribute numeric attribute + std::vector sql0(wsql.begin(), wsql.end()); + SQLRETURN ret = SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLLEN numVal = 0; + ret = SQLColAttribute(stmt, idx, fieldIdentifier, 0, 0, 0, &numVal); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(numVal, expectedAttrNumeric); +} + +void checkSQLColAttributesString(SQLHSTMT stmt, const std::wstring& wsql, + SQLUSMALLINT idx, SQLUSMALLINT fieldIdentifier, + const std::wstring& expectedAttrString) { + // Execute query and check ODBC 2.0 API SQLColAttributes string attribute + std::vector sql0(wsql.begin(), wsql.end()); + SQLRETURN ret = SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::vector strVal(ODBC_BUFFER_SIZE); + SQLSMALLINT strLen = 0; + + ret = SQLColAttributes(stmt, idx, fieldIdentifier, &strVal[0], + (SQLSMALLINT)strVal.size(), &strLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring attrStr = ConvertToWString(strVal, strLen); + EXPECT_EQ(attrStr, expectedAttrString); +} + +void checkSQLColAttributesNumeric(SQLHSTMT stmt, const std::wstring& wsql, + SQLUSMALLINT idx, SQLUSMALLINT fieldIdentifier, + SQLLEN expectedAttrNumeric) { + // Execute query and check ODBC 2.0 API SQLColAttributes numeric attribute + std::vector sql0(wsql.begin(), wsql.end()); + SQLRETURN ret = SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLLEN numVal = 0; + ret = SQLColAttributes(stmt, idx, fieldIdentifier, 0, 0, 0, &numVal); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(numVal, expectedAttrNumeric); +} + TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { // Check table pattern and column pattern returns all columns this->connect(); @@ -981,4 +1245,916 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsInvalidTablePattern) { this->disconnect(); } +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeAllTypes) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkSQLColAttribute(this->stmt, 1, + std::wstring(L"bigint_col"), // expectedColmnName + SQL_BIGINT, // expectedDataType + SQL_BIGINT, // expectedConciseType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 2, + std::wstring(L"char_col"), // expectedColmnName + SQL_WVARCHAR, // expectedDataType + SQL_WVARCHAR, // expectedConciseType + 0, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 0, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 0, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 0, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 3, + std::wstring(L"varbinary_col"), // expectedColmnName + SQL_BINARY, // expectedDataType + SQL_BINARY, // expectedConciseType + 0, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 0, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 0, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 0, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 4, + std::wstring(L"double_col"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + SQL_DOUBLE, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesAllTypesODBCVer2) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + checkSQLColAttributes(this->stmt, 1, + std::wstring(L"bigint_col"), // expectedColmnName + SQL_BIGINT, // expectedDataType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 2, + std::wstring(L"char_col"), // expectedColmnName + SQL_WVARCHAR, // expectedDataType + 0, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 0, // expectedLength + 0, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 3, + std::wstring(L"varbinary_col"), // expectedColmnName + SQL_BINARY, // expectedDataType + 0, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 0, // expectedLength + 0, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 4, + std::wstring(L"double_col"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeAllTypes) { + // Test assumes there is a table $scratch.ODBCTest in remote server + this->connect(); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkSQLColAttribute(this->stmt, 1, + std::wstring(L"sinteger_max"), // expectedColmnName + SQL_INTEGER, // expectedDataType + SQL_INTEGER, // expectedConciseType + 11, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 4, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 4, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 4, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 2, + std::wstring(L"sbigint_max"), // expectedColmnName + SQL_BIGINT, // expectedDataType + SQL_BIGINT, // expectedConciseType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 3, + std::wstring(L"decimal_positive"), // expectedColmnName + SQL_DECIMAL, // expectedDataType + SQL_DECIMAL, // expectedConciseType + 40, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 19, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 19, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 40, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 4, + std::wstring(L"float_max"), // expectedColmnName + SQL_FLOAT, // expectedDataType + SQL_FLOAT, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 5, + std::wstring(L"double_max"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + SQL_DOUBLE, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 6, + std::wstring(L"bit_true"), // expectedColmnName + SQL_BIT, // expectedDataType + SQL_BIT, // expectedConciseType + 1, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 1, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 1, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 1, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 7, + std::wstring(L"date_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TYPE_DATE, // expectedConciseType + 10, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 10, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 10, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 6, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 8, + std::wstring(L"time_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TYPE_TIME, // expectedConciseType + 12, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 12, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 12, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 6, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 9, + std::wstring(L"timestamp_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TYPE_TIMESTAMP, // expectedConciseType + 23, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 23, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 23, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 16, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeAllTypesODBCVer2) { + // Test assumes there is a table $scratch.ODBCTest in remote server + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkSQLColAttribute(this->stmt, 1, + std::wstring(L"sinteger_max"), // expectedColmnName + SQL_INTEGER, // expectedDataType + SQL_INTEGER, // expectedConciseType + 11, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 4, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 4, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 4, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 2, + std::wstring(L"sbigint_max"), // expectedColmnName + SQL_BIGINT, // expectedDataType + SQL_BIGINT, // expectedConciseType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 3, + std::wstring(L"decimal_positive"), // expectedColmnName + SQL_DECIMAL, // expectedDataType + SQL_DECIMAL, // expectedConciseType + 40, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 19, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 19, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 40, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 4, + std::wstring(L"float_max"), // expectedColmnName + SQL_FLOAT, // expectedDataType + SQL_FLOAT, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 5, + std::wstring(L"double_max"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + SQL_DOUBLE, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 6, + std::wstring(L"bit_true"), // expectedColmnName + SQL_BIT, // expectedDataType + SQL_BIT, // expectedConciseType + 1, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 1, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 1, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 1, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 7, + std::wstring(L"date_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_DATE, // expectedConciseType + 10, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 10, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 10, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 6, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 8, + std::wstring(L"time_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TIME, // expectedConciseType + 12, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 12, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 12, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 6, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 9, + std::wstring(L"timestamp_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TIMESTAMP, // expectedConciseType + 23, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 23, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 23, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 16, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributesAllTypesODBCVer2) { + // Tests ODBC 2.0 API SQLColAttributes + // Test assumes there is a table $scratch.ODBCTest in remote server + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkSQLColAttributes(this->stmt, 1, + std::wstring(L"sinteger_max"), // expectedColmnName + SQL_INTEGER, // expectedDataType + 11, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 4, // expectedLength + 4, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 2, + std::wstring(L"sbigint_max"), // expectedColmnName + SQL_BIGINT, // expectedDataType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 3, + std::wstring(L"decimal_positive"), // expectedColmnName + SQL_DECIMAL, // expectedDataType + 40, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 19, // expectedLength + 19, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 4, + std::wstring(L"float_max"), // expectedColmnName + SQL_FLOAT, // expectedDataType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 5, + std::wstring(L"double_max"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 6, + std::wstring(L"bit_true"), // expectedColmnName + SQL_BIT, // expectedDataType + 1, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 1, // expectedLength + 1, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 7, + std::wstring(L"date_max"), // expectedColmnName + SQL_DATE, // expectedDataType + 10, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 10, // expectedLength + 10, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 8, + std::wstring(L"time_max"), // expectedColmnName + SQL_TIME, // expectedDataType + 12, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 12, // expectedLength + 12, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 9, + std::wstring(L"timestamp_max"), // expectedColmnName + SQL_TIMESTAMP, // expectedDataType + 23, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 23, // expectedLength + 23, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributeCaseSensitive) { + // Arrow limitation: returns SQL_FALSE for case sensitive column + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Int column + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_CASE_SENSITIVE, SQL_FALSE); + SQLFreeStmt(this->stmt, SQL_CLOSE); + // Varchar column + checkSQLColAttributeNumeric(this->stmt, wsql, 28, SQL_DESC_CASE_SENSITIVE, SQL_FALSE); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributesCaseSensitive) { + // Arrow limitation: returns SQL_FALSE for case sensitive column + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Int column + checkSQLColAttributesNumeric(this->stmt, wsql, 1, SQL_COLUMN_CASE_SENSITIVE, SQL_FALSE); + SQLFreeStmt(this->stmt, SQL_CLOSE); + // Varchar column + checkSQLColAttributesNumeric(this->stmt, wsql, 28, SQL_COLUMN_CASE_SENSITIVE, + SQL_FALSE); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeUniqueValue) { + // Mock server limitation: returns false for auto-increment column + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_AUTO_UNIQUE_VALUE, SQL_FALSE); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesAutoIncrement) { + // Tests ODBC 2.0 API SQLColAttributes + // Mock server limitation: returns false for auto-increment column + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_COLUMN_AUTO_INCREMENT, SQL_FALSE); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeBaseTableName) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_BASE_TABLE_NAME, + std::wstring(L"AllTypesTable")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesTableName) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TABLE_NAME, + std::wstring(L"AllTypesTable")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeCatalogName) { + // Mock server limitattion: mock doesn't return catalog for result metadata, + // and the defautl catalog should be 'main' + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_CATALOG_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeCatalogName) { + // Remote server does not have catalogs + this->connect(); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_CATALOG_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, 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->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_COLUMN_QUALIFIER_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributesQualifierName) { + // Remote server does not have catalogs + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_COLUMN_QUALIFIER_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributeCount) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Pass 0 as column number, driver should ignore it + checkSQLColAttributeNumeric(this->stmt, wsql, 0, SQL_DESC_COUNT, 32); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeLocalTypeName) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Mock server doesn't have local type name + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_LOCAL_TYPE_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeLocalTypeName) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_LOCAL_TYPE_NAME, + std::wstring(L"INTEGER")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeSchemaName) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't have schemas + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_SCHEMA_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeSchemaName) { + // Test assumes there is a table $scratch.ODBCTest in remote server + this->connect(); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + // Remote server limitation: doesn't return schema name, expected schema name is + // $scratch + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_SCHEMA_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesOwnerName) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't have schemas + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_OWNER_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributesOwnerName) { + // Test assumes there is a table $scratch.ODBCTest in remote server + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + // Remote server limitation: doesn't return schema name, expected schema name is + // $scratch + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_OWNER_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeTableName) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TABLE_NAME, + std::wstring(L"AllTypesTable")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeTypeName) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't return data source-dependent data type name + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TYPE_NAME, std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeTypeName) { + this->connect(); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TYPE_NAME, + std::wstring(L"INTEGER")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesTypeName) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't return data source-dependent data type name + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TYPE_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributesTypeName) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TYPE_NAME, + std::wstring(L"INTEGER")); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributeUnnamed) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_UNNAMED, SQL_NAMED); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributeUpdatable) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Mock server and remote server do not return updatable information + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_UPDATABLE, + SQL_ATTR_READWRITE_UNKNOWN); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributesUpdatable) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Mock server and remote server do not return updatable information + checkSQLColAttributesNumeric(this->stmt, wsql, 1, SQL_COLUMN_UPDATABLE, + SQL_ATTR_READWRITE_UNKNOWN); + + this->disconnect(); +} } // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc index e39cce54e411..e0062f06da18 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc @@ -408,6 +408,19 @@ bool writeDSN(Connection::ConnPropertyMap properties) { return RegisterDsn(config, wDriver.c_str()); } +std::wstring ConvertToWString(const std::vector& strVal, SQLSMALLINT strLen) { + std::wstring attrStr; + if (strLen == 0) { + attrStr = std::wstring(&strVal[0]); + } else { + EXPECT_GT(strLen, 0); + EXPECT_LE(strLen, static_cast(ODBC_BUFFER_SIZE)); + attrStr = + std::wstring(strVal.begin(), strVal.begin() + strLen / ODBC::GetSqlWCharSize()); + } + return attrStr; +} + void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected) { SQLWCHAR buf[1024]; SQLLEN bufLen = sizeof(buf) * ODBC::GetSqlWCharSize(); diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h index a93a633d754c..101c2fe05666 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h @@ -192,6 +192,12 @@ bool writeDSN(std::string connection_str); /// \return true on success bool writeDSN(Connection::ConnPropertyMap properties); +/// \brief Check wide char vector and convert into wstring +/// \param[in] strVal Vector of SQLWCHAR. +/// \param[in] strLen length of string, in bytes. +/// \return wstring +std::wstring ConvertToWString(const std::vector& strVal, SQLSMALLINT strLen); + /// \brief Check wide string column. /// \param[in] stmt Statement. /// \param[in] colId Column ID to check. diff --git a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc index a490457c3a35..000e0b05a719 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc @@ -30,7 +30,6 @@ #include "gtest/gtest.h" namespace arrow::flight::sql::odbc { - TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectSimpleQuery) { this->connect(); @@ -2235,5 +2234,4 @@ TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExtendedFetchQueryNullIndicator) { this->disconnect(); } - } // namespace arrow::flight::sql::odbc