diff --git a/cpp/src/arrow/flight/sql/odbc/entry_points.cc b/cpp/src/arrow/flight/sql/odbc/entry_points.cc index 54d22ade54c5..4b9960f4489f 100644 --- a/cpp/src/arrow/flight/sql/odbc/entry_points.cc +++ b/cpp/src/arrow/flight/sql/odbc/entry_points.cc @@ -198,16 +198,9 @@ SQLRETURN SQL_API SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT schemaNameLength, SQLWCHAR* tableName, SQLSMALLINT tableNameLength, SQLWCHAR* columnName, SQLSMALLINT columnNameLength) { - LOG_DEBUG( - "SQLColumnsW called with stmt: {}, catalogName: {}, catalogNameLength: " - "{}, " - "schemaName: {}, schemaNameLength: {}, tableName: {}, tableNameLength: {}, " - "columnName: {}, " - "columnNameLength: {}", - stmt, fmt::ptr(catalogName), catalogNameLength, fmt::ptr(schemaName), - schemaNameLength, fmt::ptr(tableName), tableNameLength, fmt::ptr(columnName), - columnNameLength); - return SQL_ERROR; + return arrow::SQLColumns(stmt, catalogName, catalogNameLength, schemaName, + schemaNameLength, tableName, tableNameLength, columnName, + columnNameLength); } SQLRETURN SQL_API SQLError(SQLHENV handleType, SQLHDBC handle, SQLHSTMT hstmt, diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index 89f08ffe8d80..5553db102761 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -1112,4 +1112,39 @@ SQLRETURN SQLRowCount(SQLHSTMT stmt, SQLLEN* rowCountPtr) { return SQL_SUCCESS; }); } + +SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, + SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, + SQLWCHAR* tableName, SQLSMALLINT tableNameLength, + SQLWCHAR* columnName, SQLSMALLINT columnNameLength) { + // GH-47159: Return NUM_PREC_RADIX based on whether COLUMN_SIZE contains number of + // digits or bits + LOG_DEBUG( + "SQLColumnsW called with stmt: {}, catalogName: {}, catalogNameLength: " + "{}, " + "schemaName: {}, schemaNameLength: {}, tableName: {}, tableNameLength: {}, " + "columnName: {}, " + "columnNameLength: {}", + stmt, fmt::ptr(catalogName), catalogNameLength, fmt::ptr(schemaName), + schemaNameLength, fmt::ptr(tableName), tableNameLength, fmt::ptr(columnName), + columnNameLength); + + using ODBC::ODBCStatement; + using ODBC::SqlWcharToString; + + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + + std::string catalog = SqlWcharToString(catalogName, catalogNameLength); + std::string schema = SqlWcharToString(schemaName, schemaNameLength); + std::string table = SqlWcharToString(tableName, tableNameLength); + std::string column = SqlWcharToString(columnName, columnNameLength); + + statement->GetColumns(catalogName ? &catalog : nullptr, + schemaName ? &schema : nullptr, tableName ? &table : nullptr, + columnName ? &column : nullptr); + + 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 e31a06c16666..b1b5c5a30335 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.h @@ -79,4 +79,8 @@ SQLRETURN SQLGetData(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType SQLRETURN SQLMoreResults(SQLHSTMT stmt); SQLRETURN SQLNumResultCols(SQLHSTMT stmt, SQLSMALLINT* columnCountPtr); SQLRETURN SQLRowCount(SQLHSTMT stmt, SQLLEN* rowCountPtr); +SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, + SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, + SQLWCHAR* tableName, SQLSMALLINT tableNameLength, + SQLWCHAR* columnName, SQLSMALLINT columnNameLength); } // namespace arrow diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt index 952b1d36cc88..3bb1ac697e90 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt @@ -31,6 +31,7 @@ set(ARROW_FLIGHT_SQL_MOCK_SERVER_SRCS add_arrow_test(flight_sql_odbc_test SOURCES + columns_test.cc connection_attr_test.cc connection_info_test.cc statement_attr_test.cc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc new file mode 100644 index 000000000000..a05b8cd0f3a8 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -0,0 +1,984 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +#include "arrow/flight/sql/odbc/tests/odbc_test_suite.h" + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include + +#include "gtest/gtest.h" + +// TODO: add tests with SQLDescribeCol to check metadata of SQLColumns for ODBC 2 and +// ODBC 3. + +namespace arrow::flight::sql::odbc { +void checkSQLColumns( + SQLHSTMT stmt, const std::wstring& expectedTable, const std::wstring& expectedColumn, + const SQLINTEGER& expectedDataType, const std::wstring& expectedTypeName, + const SQLINTEGER& expectedColumnSize, const SQLINTEGER& expectedBufferLength, + const SQLSMALLINT& expectedDecimalDigits, const SQLSMALLINT& expectedNumPrecRadix, + const SQLSMALLINT& expectedNullable, const SQLSMALLINT& expectedSqlDataType, + const SQLSMALLINT& expectedDateTimeSub, const SQLINTEGER& expectedOctetCharLength, + const SQLINTEGER& expectedOrdinalPosition, const std::wstring& expectedIsNullable) { + CheckStringColumnW(stmt, 3, expectedTable); // table name + CheckStringColumnW(stmt, 4, expectedColumn); // column name + + CheckIntColumn(stmt, 5, expectedDataType); // data type + + CheckStringColumnW(stmt, 6, expectedTypeName); // type name + + CheckIntColumn(stmt, 7, expectedColumnSize); // column size + CheckIntColumn(stmt, 8, expectedBufferLength); // buffer length + + CheckSmallIntColumn(stmt, 9, expectedDecimalDigits); // decimal digits + CheckSmallIntColumn(stmt, 10, expectedNumPrecRadix); // num prec radix + CheckSmallIntColumn(stmt, 11, + expectedNullable); // nullable + + CheckNullColumnW(stmt, 12); // remarks + CheckNullColumnW(stmt, 13); // column def + + CheckSmallIntColumn(stmt, 14, expectedSqlDataType); // sql data type + CheckSmallIntColumn(stmt, 15, expectedDateTimeSub); // sql date type sub + CheckIntColumn(stmt, 16, expectedOctetCharLength); // char octet length + CheckIntColumn(stmt, 17, + expectedOrdinalPosition); // oridinal position + + CheckStringColumnW(stmt, 18, expectedIsNullable); // is nullable +} + +void checkMockSQLColumns( + SQLHSTMT stmt, const std::wstring& expectedCatalog, const std::wstring& expectedTable, + const std::wstring& expectedColumn, const SQLINTEGER& expectedDataType, + const std::wstring& expectedTypeName, const SQLINTEGER& expectedColumnSize, + const SQLINTEGER& expectedBufferLength, const SQLSMALLINT& expectedDecimalDigits, + const SQLSMALLINT& expectedNumPrecRadix, const SQLSMALLINT& expectedNullable, + const SQLSMALLINT& expectedSqlDataType, const SQLSMALLINT& expectedDateTimeSub, + const SQLINTEGER& expectedOctetCharLength, const SQLINTEGER& expectedOrdinalPosition, + const std::wstring& expectedIsNullable) { + CheckStringColumnW(stmt, 1, expectedCatalog); // catalog + CheckNullColumnW(stmt, 2); // schema + + checkSQLColumns(stmt, expectedTable, expectedColumn, expectedDataType, expectedTypeName, + expectedColumnSize, expectedBufferLength, expectedDecimalDigits, + expectedNumPrecRadix, expectedNullable, expectedSqlDataType, + expectedDateTimeSub, expectedOctetCharLength, expectedOrdinalPosition, + expectedIsNullable); +} + +void checkRemoteSQLColumns( + SQLHSTMT stmt, const std::wstring& expectedSchema, const std::wstring& expectedTable, + const std::wstring& expectedColumn, const SQLINTEGER& expectedDataType, + const std::wstring& expectedTypeName, const SQLINTEGER& expectedColumnSize, + const SQLINTEGER& expectedBufferLength, const SQLSMALLINT& expectedDecimalDigits, + const SQLSMALLINT& expectedNumPrecRadix, const SQLSMALLINT& expectedNullable, + const SQLSMALLINT& expectedSqlDataType, const SQLSMALLINT& expectedDateTimeSub, + const SQLINTEGER& expectedOctetCharLength, const SQLINTEGER& expectedOrdinalPosition, + const std::wstring& expectedIsNullable) { + CheckNullColumnW(stmt, 1); // catalog + CheckStringColumnW(stmt, 2, expectedSchema); // schema + checkSQLColumns(stmt, expectedTable, expectedColumn, expectedDataType, expectedTypeName, + expectedColumnSize, expectedBufferLength, expectedDecimalDigits, + expectedNumPrecRadix, expectedNullable, expectedSqlDataType, + expectedDateTimeSub, expectedOctetCharLength, expectedOrdinalPosition, + expectedIsNullable); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { + // Check table pattern and column pattern returns all columns + this->connect(); + + // Attempt to get all columns + SQLWCHAR tablePattern[] = L"%"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // mock limitation: SQLite mock server returns 10 for bigint size when spec indicates + // should be 19 + // DECIMAL_DIGITS should be 0 for bigint type since it is exact + // mock limitation: SQLite mock server returns 10 for bigint decimal digits when spec + // indicates should be 0 + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 2nd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"foreignName"), // expectedColumn + SQL_WVARCHAR, // expectedDataType + std::wstring(L"WVARCHAR"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // varchar(100), the ODBC spec expects 100) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_WVARCHAR, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 3rd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"value"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 4th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 5th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"keyName"), // expectedColumn + SQL_WVARCHAR, // expectedDataType + std::wstring(L"WVARCHAR"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // varchar(100), the ODBC spec expects 100) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_WVARCHAR, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 6th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"value"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 7th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"foreignId"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 4, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { + // Limitation: Mock server returns incorrect values for column size for some columns. + // For character and binary type columns, the driver calculates buffer length and char + // octet length from column size. + + // Checks filtering table with table name pattern + this->connect(); + this->CreateTableAllDataType(); + + // Attempt to get all columns from AllTypesTable + SQLWCHAR tablePattern[] = L"AllTypesTable"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Fetch SQLColumn data for 1st column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"AllTypesTable"), // expectedTable + std::wstring(L"bigint_col"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock server limitation: returns 10, + // the ODBC spec expects 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock server limitation: returns 15, + // the ODBC spec expects 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check SQLColumn data for 2nd column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"AllTypesTable"), // expectedTable + std::wstring(L"char_col"), // expectedColumn + SQL_WVARCHAR, // expectedDataType + std::wstring(L"WVARCHAR"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // varchar(100), the ODBC spec expects 100) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_WVARCHAR, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check SQLColumn data for 3rd column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"AllTypesTable"), // expectedTable + std::wstring(L"varbinary_col"), // expectedColumn + SQL_BINARY, // expectedDataType + std::wstring(L"BINARY"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // BLOB column, spec expects binary data limit) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BINARY, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check SQLColumn data for 4th column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"AllTypesTable"), // expectedTable + std::wstring(L"double_col"), // expectedColumn + SQL_DOUBLE, // expectedDataType + std::wstring(L"DOUBLE"), // expectedTypeName + 15, // expectedColumnSize + 8, // expectedBufferLength + 15, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DOUBLE, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 4, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There should be no more column data + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsUnicode) { + // Limitation: Mock server returns incorrect values for column size for some columns. + // For character and binary type columns, the driver calculates buffer length and char + // octet length from column size. + this->connect(); + this->CreateUnicodeTable(); + + // Attempt to get all columns + SQLWCHAR tablePattern[] = L"数据"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check SQLColumn data for 1st column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"数据"), // expectedTable + std::wstring(L"资料"), // expectedColumn + SQL_WVARCHAR, // expectedDataType + std::wstring(L"WVARCHAR"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // varchar(100), spec expects 100) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_WVARCHAR, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There should be no more column data + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColumnsAllTypes) { + // GH-47159: Return NUM_PREC_RADIX based on whether COLUMN_SIZE contains number of + // digits or bits + this->connect(); + + SQLWCHAR tablePattern[] = L"ODBCTest"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check 1st Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"sinteger_max"), // expectedColumn + SQL_INTEGER, // expectedDataType + std::wstring(L"INTEGER"), // expectedTypeName + 32, // expectedColumnSize (remote server returns number of bits) + 4, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_INTEGER, // expectedSqlDataType + NULL, // expectedDateTimeSub + 4, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 2nd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"sbigint_max"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 64, // expectedColumnSize (remote server returns number of bits) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 3rd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"decimal_positive"), // expectedColumn + SQL_DECIMAL, // expectedDataType + std::wstring(L"DECIMAL"), // expectedTypeName + 38, // expectedColumnSize + 19, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DECIMAL, // expectedSqlDataType + NULL, // expectedDateTimeSub + 2, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 4th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"float_max"), // expectedColumn + SQL_FLOAT, // expectedDataType + std::wstring(L"FLOAT"), // expectedTypeName + 24, // expectedColumnSize (precision bits from IEEE 754) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_FLOAT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 4, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 5th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"double_max"), // expectedColumn + SQL_DOUBLE, // expectedDataType + std::wstring(L"DOUBLE"), // expectedTypeName + 53, // expectedColumnSize (precision bits from IEEE 754) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DOUBLE, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 5, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 6th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"bit_true"), // expectedColumn + SQL_BIT, // expectedDataType + std::wstring(L"BOOLEAN"), // expectedTypeName + 0, // expectedColumnSize (limitation: remote server remote server + // returns 0, should be 1) + 1, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 1, // expectedOctetCharLength + 6, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // ODBC ver 3 returns SQL_TYPE_DATE, SQL_TYPE_TIME, and SQL_TYPE_TIMESTAMP in the + // DATA_TYPE field + + // Check 7th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"date_max"), // expectedColumn + SQL_TYPE_DATE, // expectedDataType + std::wstring(L"DATE"), // expectedTypeName + 0, // expectedColumnSize (limitation: remote server returns 0, should be 10) + 10, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_DATE, // expectedDateTimeSub + 6, // expectedOctetCharLength + 7, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 8th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"time_max"), // expectedColumn + SQL_TYPE_TIME, // expectedDataType + std::wstring(L"TIME"), // expectedTypeName + 3, // expectedColumnSize (limitation: should be 9+fractional digits) + 12, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_TIME, // expectedDateTimeSub + 6, // expectedOctetCharLength + 8, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 9th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"timestamp_max"), // expectedColumn + SQL_TYPE_TIMESTAMP, // expectedDataType + std::wstring(L"TIMESTAMP"), // expectedTypeName + 3, // expectedColumnSize (limitation: should be 20+fractional digits) + 23, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_TIMESTAMP, // expectedDateTimeSub + 16, // expectedOctetCharLength + 9, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There is no more column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColumnsAllTypesODBCVer2) { + // GH-47159: Return NUM_PREC_RADIX based on whether COLUMN_SIZE contains number of + // digits or bits + this->connect(SQL_OV_ODBC2); + + SQLWCHAR tablePattern[] = L"ODBCTest"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check 1st Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"sinteger_max"), // expectedColumn + SQL_INTEGER, // expectedDataType + std::wstring(L"INTEGER"), // expectedTypeName + 32, // expectedColumnSize (remote server returns number of bits) + 4, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_INTEGER, // expectedSqlDataType + NULL, // expectedDateTimeSub + 4, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 2nd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"sbigint_max"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 64, // expectedColumnSize (remote server returns number of bits) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 3rd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"decimal_positive"), // expectedColumn + SQL_DECIMAL, // expectedDataType + std::wstring(L"DECIMAL"), // expectedTypeName + 38, // expectedColumnSize + 19, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DECIMAL, // expectedSqlDataType + NULL, // expectedDateTimeSub + 2, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 4th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"float_max"), // expectedColumn + SQL_FLOAT, // expectedDataType + std::wstring(L"FLOAT"), // expectedTypeName + 24, // expectedColumnSize (precision bits from IEEE 754) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_FLOAT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 4, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 5th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"double_max"), // expectedColumn + SQL_DOUBLE, // expectedDataType + std::wstring(L"DOUBLE"), // expectedTypeName + 53, // expectedColumnSize (precision bits from IEEE 754) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DOUBLE, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 5, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 6th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"bit_true"), // expectedColumn + SQL_BIT, // expectedDataType + std::wstring(L"BOOLEAN"), // expectedTypeName + 0, // expectedColumnSize (limitation: remote server remote server + // returns 0, should be 1) + 1, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 1, // expectedOctetCharLength + 6, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // ODBC ver 2 returns SQL_DATE, SQL_TIME, and SQL_TIMESTAMP in the DATA_TYPE field + + // Check 7th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"date_max"), // expectedColumn + SQL_DATE, // expectedDataType + std::wstring(L"DATE"), // expectedTypeName + 0, // expectedColumnSize (limitation: remote server returns 0, should be 10) + 10, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_DATE, // expectedDateTimeSub + 6, // expectedOctetCharLength + 7, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 8th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"time_max"), // expectedColumn + SQL_TIME, // expectedDataType + std::wstring(L"TIME"), // expectedTypeName + 3, // expectedColumnSize (limitation: should be 9+fractional digits) + 12, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_TIME, // expectedDateTimeSub + 6, // expectedOctetCharLength + 8, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 9th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"timestamp_max"), // expectedColumn + SQL_TIMESTAMP, // expectedDataType + std::wstring(L"TIMESTAMP"), // expectedTypeName + 3, // expectedColumnSize (limitation: should be 20+fractional digits) + 23, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_TIMESTAMP, // expectedDateTimeSub + 16, // expectedOctetCharLength + 9, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There is no more column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsColumnPattern) { + // Checks filtering table with column name pattern. + // Only check table and column name + this->connect(); + + SQLWCHAR tablePattern[] = L"%"; + SQLWCHAR columnPattern[] = L"id"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check 1st Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 2nd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There is no more column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsTableColumnPattern) { + // Checks filtering table with table and column name pattern. + // Only check table and column name + this->connect(); + + SQLWCHAR tablePattern[] = L"foreignTable"; + SQLWCHAR columnPattern[] = L"id"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check 1st Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There is no more column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsInvalidTablePattern) { + this->connect(); + + SQLWCHAR tablePattern[] = L"non-existent-table"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // There is no column from filter + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + 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 a8da14c6ecf2..e39cce54e411 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 @@ -27,13 +27,14 @@ namespace arrow::flight::sql::odbc { -void FlightSQLODBCRemoteTestBase::allocEnvConnHandles() { +void FlightSQLODBCRemoteTestBase::allocEnvConnHandles(SQLINTEGER odbc_ver) { // Allocate an environment handle SQLRETURN ret = SQLAllocEnv(&env); EXPECT_EQ(ret, SQL_SUCCESS); - ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, + reinterpret_cast(static_cast(odbc_ver)), 0); EXPECT_EQ(ret, SQL_SUCCESS); @@ -43,8 +44,8 @@ void FlightSQLODBCRemoteTestBase::allocEnvConnHandles() { EXPECT_EQ(ret, SQL_SUCCESS); } -void FlightSQLODBCRemoteTestBase::connect() { - allocEnvConnHandles(); +void FlightSQLODBCRemoteTestBase::connect(SQLINTEGER odbc_ver) { + allocEnvConnHandles(odbc_ver); std::string connect_str = getConnectionString(); connectWithString(connect_str); } @@ -273,6 +274,42 @@ std::wstring FlightSQLODBCMockTestBase::getQueryAllDataTypes() { return wsql; } +void FlightSQLODBCMockTestBase::CreateTableAllDataType() { + // Limitation on mock SQLite server: + // Only int64, float64, binary, and utf8 Arrow Types are supported by + // SQLiteFlightSqlServer::Impl::DoGetTables + ASSERT_OK(server->ExecuteSql(R"( + CREATE TABLE AllTypesTable( + bigint_col INTEGER PRIMARY KEY AUTOINCREMENT, + char_col varchar(100), + varbinary_col BLOB, + double_col REAL); + + INSERT INTO AllTypesTable ( + char_col, + varbinary_col, + double_col) VALUES ( + '1st Row', + X'31737420726F77', + 3.14159 + ); + )")); +} + +void FlightSQLODBCMockTestBase::CreateUnicodeTable() { + std::string unicodeSql = arrow::util::WideStringToUTF8( + LR"( + CREATE TABLE 数据( + 资料 varchar(100)); + + INSERT INTO 数据 (资料) VALUES ('第一行'); + INSERT INTO 数据 (资料) VALUES ('二行'); + INSERT INTO 数据 (资料) VALUES ('3rd Row'); + )") + .ValueOr(""); + ASSERT_OK(server->ExecuteSql(unicodeSql)); +} + void FlightSQLODBCMockTestBase::SetUp() { ASSERT_OK_AND_ASSIGN(auto location, Location::ForGrpcTcp("0.0.0.0", 0)); arrow::flight::FlightServerOptions options(location); @@ -371,4 +408,50 @@ bool writeDSN(Connection::ConnPropertyMap properties) { return RegisterDsn(config, wDriver.c_str()); } +void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected) { + SQLWCHAR buf[1024]; + SQLLEN bufLen = sizeof(buf) * ODBC::GetSqlWCharSize(); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, bufLen, &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(bufLen, 0); + + // returned bufLen is in bytes so convert to length in characters + size_t charCount = static_cast(bufLen) / ODBC::GetSqlWCharSize(); + std::wstring returned(buf, buf + charCount); + + EXPECT_EQ(returned, expected); +} + +void CheckNullColumnW(SQLHSTMT stmt, int colId) { + SQLWCHAR buf[1024]; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, bufLen, &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(bufLen, SQL_NULL_DATA); +} + +void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& expected) { + SQLINTEGER buf; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_LONG, &buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(buf, expected); +} + +void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& expected) { + SQLSMALLINT buf; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_SSHORT, &buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(buf, expected); +} + } // namespace arrow::flight::sql::odbc 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 21e2d899ef11..a93a633d754c 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 @@ -48,10 +48,11 @@ using driver::odbcabstraction::Connection; class FlightSQLODBCRemoteTestBase : public ::testing::Test { public: /// \brief Allocate environment and connection handles - void allocEnvConnHandles(); + void allocEnvConnHandles(SQLINTEGER odbc_ver = SQL_OV_ODBC3); /// \brief Connect to Arrow Flight SQL server using connection string defined in - /// environment variable "ARROW_FLIGHT_SQL_ODBC_CONN", allocate statement handle - void connect(); + /// environment variable "ARROW_FLIGHT_SQL_ODBC_CONN", allocate statement handle. + /// Connects using ODBC Ver 3 by default + void connect(SQLINTEGER odbc_ver = SQL_OV_ODBC3); /// \brief Connect to Arrow Flight SQL server using connection string void connectWithString(std::string connection_str); /// \brief Disconnect from server @@ -124,6 +125,11 @@ class FlightSQLODBCMockTestBase : public FlightSQLODBCRemoteTestBase { /// \brief Return a SQL query that selects all data types std::wstring getQueryAllDataTypes() override; + /// \brief run a SQL query to create a table with all data types + void CreateTableAllDataType(); + /// \brief run a SQL query to create a table with unicode name + void CreateUnicodeTable(); + int port; protected: @@ -185,4 +191,27 @@ bool writeDSN(std::string connection_str); /// \param[in] properties map. /// \return true on success bool writeDSN(Connection::ConnPropertyMap properties); + +/// \brief Check wide string column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] expected Expected value. +void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected); + +/// \brief Check wide string column value is null. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +void CheckNullColumnW(SQLHSTMT stmt, int colId); + +/// \brief Check int column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] expected Expected value. +void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& expected); + +/// \brief Check smallint column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] expected Expected value. +void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& expected); } // namespace arrow::flight::sql::odbc