From 16802f03d09abde6593c573bdaf06c2969d0c2c3 Mon Sep 17 00:00:00 2001 From: rscales Date: Wed, 16 Jul 2025 03:17:10 +0100 Subject: [PATCH 01/16] Implement SQLTables --- cpp/src/arrow/flight/sql/odbc/entry_points.cc | 13 ++------ cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 31 +++++++++++++++++++ cpp/src/arrow/flight/sql/odbc/odbc_api.h | 4 +++ .../flight/sql/odbc/tests/odbc_test_suite.cc | 13 ++++++++ .../flight/sql/odbc/tests/odbc_test_suite.h | 2 ++ 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/entry_points.cc b/cpp/src/arrow/flight/sql/odbc/entry_points.cc index e7c55d5a73a1..3cac063f88cd 100644 --- a/cpp/src/arrow/flight/sql/odbc/entry_points.cc +++ b/cpp/src/arrow/flight/sql/odbc/entry_points.cc @@ -287,14 +287,7 @@ SQLRETURN SQL_API SQLTables(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT schemaNameLength, SQLWCHAR* tableName, SQLSMALLINT tableNameLength, SQLWCHAR* tableType, SQLSMALLINT tableTypeLength) { - LOG_DEBUG( - "SQLTablesW called with stmt: {}, catalogName: {}, catalogNameLength: " - "{}, " - "schemaName: {}, schemaNameLength: {}, tableName: {}, tableNameLength: {}, " - "tableType: {}, " - "tableTypeLength: {}", - stmt, fmt::ptr(catalogName), catalogNameLength, fmt::ptr(schemaName), - schemaNameLength, fmt::ptr(tableName), tableNameLength, fmt::ptr(tableType), - tableTypeLength); - return SQL_ERROR; + return arrow::SQLTables(stmt, catalogName, catalogNameLength, schemaName, + schemaNameLength, tableName, tableNameLength, tableType, + tableTypeLength); } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index 91121726e8f1..10db206c0f70 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -1028,4 +1028,35 @@ SQLRETURN SQL_API SQLRowCount(SQLHSTMT stmt, SQLLEN* rowCountPtr) { return SQL_SUCCESS; }); } + +SQLRETURN SQLTables(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, + SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, + SQLWCHAR* tableName, SQLSMALLINT tableNameLength, SQLWCHAR* tableType, + SQLSMALLINT tableTypeLength) { + LOG_DEBUG( + "SQLTables called with stmt: {}, catalogName: {}, catalogNameLength: " + "{}, " + "schemaName: {}, schemaNameLength: {}, tableName: {}, tableNameLength: {}, " + "tableType: {}, " + "tableTypeLength: {}", + stmt, fmt::ptr(catalogName), catalogNameLength, fmt::ptr(schemaName), + schemaNameLength, fmt::ptr(tableName), tableNameLength, fmt::ptr(tableType), + tableTypeLength); + using namespace ODBC; + + 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 type = SqlWcharToString(tableType, tableTypeLength); + + statement->GetTables(catalogName ? &catalog : nullptr, schemaName ? &schema : nullptr, + tableName ? &table : nullptr, tableType ? &type : 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 504a8f545f88..b0ea8c2995a9 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.h @@ -73,4 +73,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 SQLTables(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, + SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, + SQLWCHAR* tableName, SQLSMALLINT tableNameLength, SQLWCHAR* tableType, + SQLSMALLINT tableTypeLength); } // namespace arrow 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..94561d7910ac 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 @@ -273,6 +273,19 @@ std::wstring FlightSQLODBCMockTestBase::getQueryAllDataTypes() { return wsql; } +void FlightSQLODBCMockTestBase::CreateTestTables() { + ASSERT_OK(server->ExecuteSql(R"( + CREATE TABLE TestTable ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + keyName varchar(100), + value int); + + INSERT INTO TestTable (keyName, value) VALUES ('One', 1); + INSERT INTO TestTable (keyName, value) VALUES ('Two', 0); + INSERT INTO TestTable (keyName, value) VALUES ('Three', -1); + )")); +} + void FlightSQLODBCMockTestBase::SetUp() { ASSERT_OK_AND_ASSIGN(auto location, Location::ForGrpcTcp("0.0.0.0", 0)); arrow::flight::FlightServerOptions options(location); 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 1dd71e12f737..0e0032a9c3d8 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 @@ -124,6 +124,8 @@ class FlightSQLODBCMockTestBase : public FlightSQLODBCRemoteTestBase { /// \brief Return a SQL query that selects all data types std::wstring getQueryAllDataTypes() override; + void CreateTestTables(); + int port; protected: From b5dfa495919174e6f93fbb5302195b1ee3e639c6 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 16 Jul 2025 11:24:58 -0700 Subject: [PATCH 02/16] Add draft tests --- .../flight/sql/odbc/tests/statement_test.cc | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) 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 3335d5f72469..dc650c0fa72b 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc @@ -1432,4 +1432,66 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectIgnoreInvalidBufLen) { this->disconnect(); } +//-AL- temp +TYPED_TEST(FlightSQLODBCTestBase, TestSQLTables) { + //GTEST_SKIP(); + //TEST_F(FlightSQLODBCMockTestBase, TestSQLTables) { + this->connect(); + + // Attempt to get all tables + SQLWCHAR catalogPattern[] = L"%"; + SQLWCHAR schemaPattern[] = L"%"; + SQLWCHAR tablePattern[] = L"%"; + SQLWCHAR tableTypePattern[] = L"TABLE"; + + SQLRETURN ret = SQLTables(this->stmt, catalogPattern, SQL_NTS, schemaPattern, SQL_NTS, tablePattern, + SQL_NTS, tableTypePattern, SQL_NTS); + + + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + this->disconnect(); +} + +//TYPED_TEST(FlightSQLODBCTestBase, TestSQLTablesNullptr) { +//TEST_F(FlightSQLODBCRemoteTestBase, TestSQLTablesNullptr) { +TEST_F(FlightSQLODBCMockTestBase, TestSQLTablesNullptr) { + this->connect(); + + // Attempt to get all tables + SQLWCHAR tableTypePattern[] = L"TABLE"; + + SQLRETURN ret = SQLTables(this->stmt, 0, SQL_NTS, 0, SQL_NTS, + 0, SQL_NTS, tableTypePattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + SQLCHAR char_val[1204]; + SQLLEN buf_len = sizeof(SQLCHAR) * 1204; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val, buf_len, &ind); + EXPECT_EQ(ODBC::SqlStringToString(char_val), std::string("STAGING")); + + this->disconnect(); +} + } // namespace arrow::flight::sql::odbc From 3292ac59a6a90c34f9e5f5a62f3191418664e75e Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 16 Jul 2025 14:16:35 -0700 Subject: [PATCH 03/16] Initial impl for SQLColumns --- cpp/src/arrow/flight/sql/odbc/entry_points.cc | 13 ++------ cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 33 +++++++++++++++++++ cpp/src/arrow/flight/sql/odbc/odbc_api.h | 4 +++ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/entry_points.cc b/cpp/src/arrow/flight/sql/odbc/entry_points.cc index 319dcc5f2f42..6aec4dace2ef 100644 --- a/cpp/src/arrow/flight/sql/odbc/entry_points.cc +++ b/cpp/src/arrow/flight/sql/odbc/entry_points.cc @@ -191,16 +191,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 8819bf622268..09c93f70503c 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -1110,4 +1110,37 @@ SQLRETURN SQLTables(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNam }); } +SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, + SQLWCHAR* schemaName, 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); + + 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 28e9e119dbf9..0733ca19df8b 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.h @@ -80,4 +80,8 @@ SQLRETURN SQLTables(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNam SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, SQLWCHAR* tableName, SQLSMALLINT tableNameLength, SQLWCHAR* tableType, SQLSMALLINT tableTypeLength); +SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, + SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, + SQLWCHAR* tableName, SQLSMALLINT tableNameLength, + SQLWCHAR* columnName, SQLSMALLINT columnNameLength); } // namespace arrow From a7a9826ba40e6036e3585d30f4290f2ad1905382 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 16 Jul 2025 15:02:00 -0700 Subject: [PATCH 04/16] Add test for SQLColumns --- .../flight/sql/odbc/tests/statement_test.cc | 141 ++++++++++++++---- 1 file changed, 111 insertions(+), 30 deletions(-) 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 e62c3dd010cc..35bc94980c9d 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc @@ -2152,65 +2152,146 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLBindColIndicatorOnlySQLUnbind) { this->disconnect(); } -//-AL- temp -TYPED_TEST(FlightSQLODBCTestBase, TestSQLTables) { - //GTEST_SKIP(); - //TEST_F(FlightSQLODBCMockTestBase, TestSQLTables) { - this->connect(); - - // Attempt to get all tables - SQLWCHAR catalogPattern[] = L"%"; - SQLWCHAR schemaPattern[] = L"%"; - SQLWCHAR tablePattern[] = L"%"; - SQLWCHAR tableTypePattern[] = L"TABLE"; +/** + * Check string column. + * + * @param stmt Statement. + * @param colId Column ID to check. + * @param value Expected value. + */ +void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value) { + char buf[1024]; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_CHAR, buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; + } - SQLRETURN ret = SQLTables(this->stmt, catalogPattern, SQL_NTS, schemaPattern, SQL_NTS, tablePattern, - SQL_NTS, tableTypePattern, SQL_NTS); + if (bufLen <= 0) + EXPECT_TRUE(value.empty()); + else + EXPECT_EQ(std::string(buf, static_cast(bufLen)), value); +} +/** + * Check int column. + * + * @param stmt Statement. + * @param colId Column ID to check. + * @param value Expected value. + */ +void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value) { + SQLINTEGER buf; + SQLLEN bufLen = sizeof(buf); + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_LONG, &buf, sizeof(buf), &bufLen); EXPECT_EQ(ret, SQL_SUCCESS); if (ret != SQL_SUCCESS) { - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; } - ret = SQLFetch(stmt); + EXPECT_EQ(buf, value); +} + +/** + * Check smallint column. + * + * @param stmt Statement. + * @param colId Column ID to check. + * @param value Expected value. + */ +void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& value) { + SQLSMALLINT buf; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_SSHORT, &buf, sizeof(buf), &bufLen); EXPECT_EQ(ret, SQL_SUCCESS); if (ret != SQL_SUCCESS) { - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; } - this->disconnect(); + EXPECT_EQ(buf, value); } -//TYPED_TEST(FlightSQLODBCTestBase, TestSQLTablesNullptr) { -//TEST_F(FlightSQLODBCRemoteTestBase, TestSQLTablesNullptr) { -TEST_F(FlightSQLODBCMockTestBase, TestSQLTablesNullptr) { +// -AL- TODO: work on fetching all columns. The plan is to use this test as example, +// -AL- next TODO: create table that supports all SQLite data types, +// and change table pattern to match that one table. Use a new test to verify all types. + +// if (boost::iequals(sqlite_type, "int") || boost::iequals(sqlite_type, "integer")) { +// return int64(); +//} else if (boost::iequals(sqlite_type, "REAL")) { +// return float64(); +//} else if (boost::iequals(sqlite_type, "BLOB")) { +// return binary(); +//} else if (boost::iequals(sqlite_type, "TEXT") || boost::iequals(sqlite_type, "DATE") || +// boost::istarts_with(sqlite_type, "char") || +// boost::istarts_with(sqlite_type, "varchar")) { +// return utf8(); +//} +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumns) { this->connect(); - // Attempt to get all tables - SQLWCHAR tableTypePattern[] = L"TABLE"; + // Attempt to get all columns + SQLWCHAR tablePattern[] = L"%"; + SQLWCHAR columnPattern[] = L"%"; - SQLRETURN ret = SQLTables(this->stmt, 0, SQL_NTS, 0, SQL_NTS, - 0, SQL_NTS, tableTypePattern, SQL_NTS); + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); EXPECT_EQ(ret, SQL_SUCCESS); if (ret != SQL_SUCCESS) { + // -AL- temp std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; } ret = SQLFetch(stmt); EXPECT_EQ(ret, SQL_SUCCESS); if (ret != SQL_SUCCESS) { + // -AL- temp std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; } - SQLCHAR char_val[1204]; - SQLLEN buf_len = sizeof(SQLCHAR) * 1204; - SQLLEN ind; + std::string empty = std::string(""); - ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val, buf_len, &ind); - EXPECT_EQ(ODBC::SqlStringToString(char_val), std::string("STAGING")); - + // 18 columns are returned by SQLColumns + CheckStringColumn(this->stmt, 1, std::string("main")); // catalog + CheckStringColumn(this->stmt, 2, empty); // schema + CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("id")); // column name + + CheckIntColumn(this->stmt, 5, SQL_BIGINT); // data type + + CheckStringColumn(this->stmt, 6, std::string("BIGINT")); // type name + + // mock limitation: SQLite mock server returns 10 for bigint size when spec indicates + // should be 19 + CheckIntColumn(this->stmt, 7, 10); // column size + CheckIntColumn(this->stmt, 8, 8); // buffer length + + // 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 + CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits + CheckSmallIntColumn(this->stmt, 10, 10); // num prec radix + CheckSmallIntColumn(this->stmt, 11, + SQL_NULLABLE); // nullable + + CheckStringColumn(this->stmt, 12, empty); // remarks + CheckStringColumn(this->stmt, 13, empty); // column def + + CheckSmallIntColumn(this->stmt, 14, SQL_BIGINT); // sql data type not NULL + CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub + CheckIntColumn(this->stmt, 16, 8); // char octet length + CheckIntColumn(this->stmt, 17, + 1); // oridinal position + + CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable this->disconnect(); } + } // namespace arrow::flight::sql::odbc From 2e496c85434f3a931e206438e3474d188b99018c Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 17 Jul 2025 15:17:08 -0700 Subject: [PATCH 05/16] Move SQLColumn tests to new file --- .../flight/sql/odbc/tests/CMakeLists.txt | 1 + .../flight/sql/odbc/tests/columns_test.cc | 172 ++++++++++++++++++ .../flight/sql/odbc/tests/statement_test.cc | 142 --------------- 3 files changed, 173 insertions(+), 142 deletions(-) create mode 100644 cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc 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..c437ae87a14b --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -0,0 +1,172 @@ +// 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" + +namespace arrow::flight::sql::odbc { +/** + * Check string column. + * + * @param stmt Statement. + * @param colId Column ID to check. + * @param value Expected value. + */ +void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value) { + char buf[1024]; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_CHAR, buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; + } + + if (bufLen <= 0) + EXPECT_TRUE(value.empty()); + else + EXPECT_EQ(std::string(buf, static_cast(bufLen)), value); +} + +/** + * Check int column. + * + * @param stmt Statement. + * @param colId Column ID to check. + * @param value Expected value. + */ +void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value) { + SQLINTEGER buf; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_LONG, &buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; + } + + EXPECT_EQ(buf, value); +} + +/** + * Check smallint column. + * + * @param stmt Statement. + * @param colId Column ID to check. + * @param value Expected value. + */ +void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& value) { + SQLSMALLINT buf; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_SSHORT, &buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; + } + + EXPECT_EQ(buf, value); +} + +// -AL- TODO: work on fetching all columns. The plan is to use this test as example, +// -AL- next TODO: create table that supports all SQLite data types, +// and change table pattern to match that one table. Use a new test to verify all types. + +// if (boost::iequals(sqlite_type, "int") || boost::iequals(sqlite_type, "integer")) { +// return int64(); +//} else if (boost::iequals(sqlite_type, "REAL")) { +// return float64(); +//} else if (boost::iequals(sqlite_type, "BLOB")) { +// return binary(); +//} else if (boost::iequals(sqlite_type, "TEXT") || boost::iequals(sqlite_type, "DATE") || +// boost::istarts_with(sqlite_type, "char") || +// boost::istarts_with(sqlite_type, "varchar")) { +// return utf8(); +//} +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumns) { + 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); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + std::string empty = std::string(""); + + // 18 columns are returned by SQLColumns + CheckStringColumn(this->stmt, 1, std::string("main")); // catalog + CheckStringColumn(this->stmt, 2, empty); // schema + CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("id")); // column name + + CheckIntColumn(this->stmt, 5, SQL_BIGINT); // data type + + CheckStringColumn(this->stmt, 6, std::string("BIGINT")); // type name + + // mock limitation: SQLite mock server returns 10 for bigint size when spec indicates + // should be 19 + CheckIntColumn(this->stmt, 7, 10); // column size + CheckIntColumn(this->stmt, 8, 8); // buffer length + + // 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 + CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits + CheckSmallIntColumn(this->stmt, 10, 10); // num prec radix + CheckSmallIntColumn(this->stmt, 11, + SQL_NULLABLE); // nullable + + CheckStringColumn(this->stmt, 12, empty); // remarks + CheckStringColumn(this->stmt, 13, empty); // column def + + CheckSmallIntColumn(this->stmt, 14, SQL_BIGINT); // sql data type not NULL + CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub + CheckIntColumn(this->stmt, 16, 8); // char octet length + CheckIntColumn(this->stmt, 17, + 1); // oridinal position + + CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + this->disconnect(); +} + +} // namespace arrow::flight::sql::odbc 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 35bc94980c9d..ad6891ddd0ab 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc @@ -2152,146 +2152,4 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLBindColIndicatorOnlySQLUnbind) { this->disconnect(); } -/** - * Check string column. - * - * @param stmt Statement. - * @param colId Column ID to check. - * @param value Expected value. - */ -void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value) { - char buf[1024]; - SQLLEN bufLen = sizeof(buf); - - SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_CHAR, buf, sizeof(buf), &bufLen); - EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } - - if (bufLen <= 0) - EXPECT_TRUE(value.empty()); - else - EXPECT_EQ(std::string(buf, static_cast(bufLen)), value); -} - -/** - * Check int column. - * - * @param stmt Statement. - * @param colId Column ID to check. - * @param value Expected value. - */ -void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value) { - SQLINTEGER buf; - SQLLEN bufLen = sizeof(buf); - - SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_LONG, &buf, sizeof(buf), &bufLen); - EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } - - EXPECT_EQ(buf, value); -} - -/** - * Check smallint column. - * - * @param stmt Statement. - * @param colId Column ID to check. - * @param value Expected value. - */ -void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& value) { - SQLSMALLINT buf; - SQLLEN bufLen = sizeof(buf); - - SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_SSHORT, &buf, sizeof(buf), &bufLen); - EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } - - EXPECT_EQ(buf, value); -} - -// -AL- TODO: work on fetching all columns. The plan is to use this test as example, -// -AL- next TODO: create table that supports all SQLite data types, -// and change table pattern to match that one table. Use a new test to verify all types. - -// if (boost::iequals(sqlite_type, "int") || boost::iequals(sqlite_type, "integer")) { -// return int64(); -//} else if (boost::iequals(sqlite_type, "REAL")) { -// return float64(); -//} else if (boost::iequals(sqlite_type, "BLOB")) { -// return binary(); -//} else if (boost::iequals(sqlite_type, "TEXT") || boost::iequals(sqlite_type, "DATE") || -// boost::istarts_with(sqlite_type, "char") || -// boost::istarts_with(sqlite_type, "varchar")) { -// return utf8(); -//} -TEST_F(FlightSQLODBCMockTestBase, TestSQLColumns) { - 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); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } - - ret = SQLFetch(stmt); - EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } - - std::string empty = std::string(""); - - // 18 columns are returned by SQLColumns - CheckStringColumn(this->stmt, 1, std::string("main")); // catalog - CheckStringColumn(this->stmt, 2, empty); // schema - CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("id")); // column name - - CheckIntColumn(this->stmt, 5, SQL_BIGINT); // data type - - CheckStringColumn(this->stmt, 6, std::string("BIGINT")); // type name - - // mock limitation: SQLite mock server returns 10 for bigint size when spec indicates - // should be 19 - CheckIntColumn(this->stmt, 7, 10); // column size - CheckIntColumn(this->stmt, 8, 8); // buffer length - - // 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 - CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits - CheckSmallIntColumn(this->stmt, 10, 10); // num prec radix - CheckSmallIntColumn(this->stmt, 11, - SQL_NULLABLE); // nullable - - CheckStringColumn(this->stmt, 12, empty); // remarks - CheckStringColumn(this->stmt, 13, empty); // column def - - CheckSmallIntColumn(this->stmt, 14, SQL_BIGINT); // sql data type not NULL - CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub - CheckIntColumn(this->stmt, 16, 8); // char octet length - CheckIntColumn(this->stmt, 17, - 1); // oridinal position - - CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable - this->disconnect(); -} - } // namespace arrow::flight::sql::odbc From 1f6c8c42d159c39178a1b1dabe4937a83f67a1d4 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 17 Jul 2025 15:30:23 -0700 Subject: [PATCH 06/16] Move helper functions to odbc_test_suite.h --- .../flight/sql/odbc/tests/columns_test.cc | 65 ------------------- .../flight/sql/odbc/tests/odbc_test_suite.cc | 45 +++++++++++++ .../flight/sql/odbc/tests/odbc_test_suite.h | 18 +++++ 3 files changed, 63 insertions(+), 65 deletions(-) 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 c437ae87a14b..379daa4c8169 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -27,71 +27,6 @@ #include "gtest/gtest.h" namespace arrow::flight::sql::odbc { -/** - * Check string column. - * - * @param stmt Statement. - * @param colId Column ID to check. - * @param value Expected value. - */ -void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value) { - char buf[1024]; - SQLLEN bufLen = sizeof(buf); - - SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_CHAR, buf, sizeof(buf), &bufLen); - EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } - - if (bufLen <= 0) - EXPECT_TRUE(value.empty()); - else - EXPECT_EQ(std::string(buf, static_cast(bufLen)), value); -} - -/** - * Check int column. - * - * @param stmt Statement. - * @param colId Column ID to check. - * @param value Expected value. - */ -void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value) { - SQLINTEGER buf; - SQLLEN bufLen = sizeof(buf); - - SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_LONG, &buf, sizeof(buf), &bufLen); - EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } - - EXPECT_EQ(buf, value); -} - -/** - * Check smallint column. - * - * @param stmt Statement. - * @param colId Column ID to check. - * @param value Expected value. - */ -void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& value) { - SQLSMALLINT buf; - SQLLEN bufLen = sizeof(buf); - - SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_SSHORT, &buf, sizeof(buf), &bufLen); - EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } - - EXPECT_EQ(buf, value); -} // -AL- TODO: work on fetching all columns. The plan is to use this test as example, // -AL- next TODO: create table that supports all SQLite data types, 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 94561d7910ac..d33c5dfd18d2 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 @@ -384,4 +384,49 @@ bool writeDSN(Connection::ConnPropertyMap properties) { return RegisterDsn(config, wDriver.c_str()); } +void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value) { + char buf[1024]; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_CHAR, buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; + } + + if (bufLen <= 0) + EXPECT_TRUE(value.empty()); + else + EXPECT_EQ(std::string(buf, static_cast(bufLen)), value); +} + +void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value) { + SQLINTEGER buf; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_LONG, &buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; + } + + EXPECT_EQ(buf, value); +} + +void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& value) { + SQLSMALLINT buf; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_SSHORT, &buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; + } + + EXPECT_EQ(buf, value); +} + } // 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 e2c7a94f5cde..742ec3c3aa14 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 @@ -187,4 +187,22 @@ bool writeDSN(std::string connection_str); /// \param[in] properties map. /// \return true on success bool writeDSN(Connection::ConnPropertyMap properties); + +/// \brief Check string column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] value Expected value. +void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value); + +/// \brief Check int column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] value Expected value. +void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value); + +/// \brief Check smallint column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] value Expected value. +void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& value); } // namespace arrow::flight::sql::odbc From ff8574b79f9cea10de8b147fa646ab865ff0fac5 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 17 Jul 2025 16:00:27 -0700 Subject: [PATCH 07/16] Add columns test with all supported column types --- .../flight_sql_statement_get_columns.cc | 1 + .../flight/sql/odbc/tests/columns_test.cc | 156 +++++++++++++++++- .../flight/sql/odbc/tests/odbc_test_suite.cc | 22 +++ .../flight/sql/odbc/tests/odbc_test_suite.h | 2 + 4 files changed, 180 insertions(+), 1 deletion(-) diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_columns.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_columns.cc index d3250401193d..647e7888e1bc 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_columns.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_columns.cc @@ -83,6 +83,7 @@ Result> Transform_inner( const std::shared_ptr& original, const optional& column_name_pattern, const MetadataSettings& metadata_settings) { + //-AL- this is where SQLColumn items are retrieved GetColumns_RecordBatchBuilder builder(odbc_version); GetColumns_RecordBatchBuilder::Data data; 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 379daa4c8169..26807927082c 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -59,7 +59,7 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumns) { std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; } - ret = SQLFetch(stmt); + ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); if (ret != SQL_SUCCESS) { // -AL- temp @@ -104,4 +104,158 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumns) { 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. + this->connect(); + this->CreateTableAllDataType(); + + // Attempt to get all columns + 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); + + std::string empty = std::string(""); + + CheckStringColumn(this->stmt, 1, std::string("main")); // catalog + CheckStringColumn(this->stmt, 2, empty); // schema + CheckStringColumn(this->stmt, 3, std::string("AllTypesTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("bigint_col")); // column name + + CheckIntColumn(this->stmt, 5, SQL_BIGINT); // data type + + CheckStringColumn(this->stmt, 6, std::string("BIGINT")); // type name + + // mock limitation: SQLite mock server returns 10 for bigint column size when spec + // indicates should be 19 + CheckIntColumn(this->stmt, 7, 10); // column size + CheckIntColumn(this->stmt, 8, 8); // buffer length + + // 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 + CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits + CheckSmallIntColumn(this->stmt, 10, 10); // num prec radix + CheckSmallIntColumn(this->stmt, 11, + SQL_NULLABLE); // nullable + + CheckStringColumn(this->stmt, 12, empty); // remarks + CheckStringColumn(this->stmt, 13, empty); // column def + + CheckSmallIntColumn(this->stmt, 14, SQL_BIGINT); // sql data type not NULL + CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub + CheckIntColumn(this->stmt, 16, 8); // char octet length + CheckIntColumn(this->stmt, 17, 1); // oridinal position + + CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + + // Check SQLColumn data for 2nd column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + CheckStringColumn(this->stmt, 1, std::string("main")); // catalog + CheckStringColumn(this->stmt, 2, empty); // schema + CheckStringColumn(this->stmt, 3, std::string("AllTypesTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("char_col")); // column name + + CheckIntColumn(this->stmt, 5, SQL_WVARCHAR); // data type + + CheckStringColumn(this->stmt, 6, std::string("WVARCHAR")); // type name + + // mock limitation: SQLite mock server returns 0 for varchar(100) column size when spec + // indicates should be 100. + CheckIntColumn(this->stmt, 7, 0); // column size + CheckIntColumn(this->stmt, 8, 0); // buffer length + + CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits + CheckSmallIntColumn(this->stmt, 10, 0); // num prec radix + CheckSmallIntColumn(this->stmt, 11, + SQL_NULLABLE); // nullable + + CheckStringColumn(this->stmt, 12, empty); // remarks + CheckStringColumn(this->stmt, 13, empty); // column def + + CheckSmallIntColumn(this->stmt, 14, SQL_WVARCHAR); // sql data type not NULL + CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub + CheckIntColumn(this->stmt, 16, 0); // char octet length + CheckIntColumn(this->stmt, 17, 2); // oridinal position + + CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + + // Check SQLColumn data for 3rd column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + CheckStringColumn(this->stmt, 1, std::string("main")); // catalog + CheckStringColumn(this->stmt, 2, empty); // schema + CheckStringColumn(this->stmt, 3, std::string("AllTypesTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("varbinary_col")); // column name + + CheckIntColumn(this->stmt, 5, SQL_BINARY); // data type + + CheckStringColumn(this->stmt, 6, std::string("BINARY")); // type name + + // mock limitation: SQLite mock server returns 0 for BLOB column size when spec + // indicates should be binary data limit. + CheckIntColumn(this->stmt, 7, 0); // column size + CheckIntColumn(this->stmt, 8, 0); // buffer length + + CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits + CheckSmallIntColumn(this->stmt, 10, 0); // num prec radix + CheckSmallIntColumn(this->stmt, 11, + SQL_NULLABLE); // nullable + + CheckStringColumn(this->stmt, 12, empty); // remarks + CheckStringColumn(this->stmt, 13, empty); // column def + + CheckSmallIntColumn(this->stmt, 14, SQL_BINARY); // sql data type not NULL + CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub + CheckIntColumn(this->stmt, 16, 0); // char octet length + CheckIntColumn(this->stmt, 17, 3); // oridinal position + + CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + + // Check SQLColumn data for 4th column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + CheckStringColumn(this->stmt, 1, std::string("main")); // catalog + CheckStringColumn(this->stmt, 2, empty); // schema + CheckStringColumn(this->stmt, 3, std::string("AllTypesTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("double_col")); // column name + + CheckIntColumn(this->stmt, 5, SQL_DOUBLE); // data type + + CheckStringColumn(this->stmt, 6, std::string("DOUBLE")); // type name + + CheckIntColumn(this->stmt, 7, 15); // column size + CheckIntColumn(this->stmt, 8, 8); // buffer length + + CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits + CheckSmallIntColumn(this->stmt, 10, 2); // num prec radix + CheckSmallIntColumn(this->stmt, 11, + SQL_NULLABLE); // nullable + + CheckStringColumn(this->stmt, 12, empty); // remarks + CheckStringColumn(this->stmt, 13, empty); // column def + + CheckSmallIntColumn(this->stmt, 14, SQL_DOUBLE); // sql data type not NULL + CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub + CheckIntColumn(this->stmt, 16, 8); // char octet length + CheckIntColumn(this->stmt, 17, 4); // oridinal position + + CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + + 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 d33c5dfd18d2..ad1b7cf86e73 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 @@ -286,6 +286,28 @@ void FlightSQLODBCMockTestBase::CreateTestTables() { )")); } +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::SetUp() { ASSERT_OK_AND_ASSIGN(auto location, Location::ForGrpcTcp("0.0.0.0", 0)); arrow::flight::FlightServerOptions options(location); 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 742ec3c3aa14..3859a308ca90 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 @@ -125,6 +125,8 @@ class FlightSQLODBCMockTestBase : public FlightSQLODBCRemoteTestBase { std::wstring getQueryAllDataTypes() override; void CreateTestTables(); + /// \brief run a SQL query to create a table with all data types + void CreateTableAllDataType(); int port; From 49e08a87d1e73abce8cc1bb751a135ce8b490380 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 17 Jul 2025 16:50:39 -0700 Subject: [PATCH 08/16] Draft code for creating unicode table and tests --- .../flight/sql/odbc/tests/columns_test.cc | 57 +++++++++++++++++++ .../flight/sql/odbc/tests/odbc_test_suite.cc | 32 +++++++++++ .../flight/sql/odbc/tests/odbc_test_suite.h | 8 +++ 3 files changed, 97 insertions(+) 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 26807927082c..fd3c025adf1d 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -258,4 +258,61 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { 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); + + // Skip the check for 1st column in unicode table 数据 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::string empty = std::string(""); + + // Check SQLColumn data for 2nd column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + CheckStringColumn(this->stmt, 1, std::string("main")); // catalog + CheckStringColumn(this->stmt, 2, empty); // schema + CheckStringColumnW(this->stmt, 3, std::wstring(L"数据")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"资料")); // column name + + CheckIntColumn(this->stmt, 5, SQL_WVARCHAR); // data type + + CheckStringColumn(this->stmt, 6, std::string("WVARCHAR")); // type name + + // mock limitation: SQLite mock server returns 0 for varchar(100) column size when spec + // indicates should be 100. + CheckIntColumn(this->stmt, 7, 0); // column size + CheckIntColumn(this->stmt, 8, 0); // buffer length + + CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits + CheckSmallIntColumn(this->stmt, 10, 0); // num prec radix + CheckSmallIntColumn(this->stmt, 11, + SQL_NULLABLE); // nullable + + CheckStringColumn(this->stmt, 12, empty); // remarks + CheckStringColumn(this->stmt, 13, empty); // column def + + CheckSmallIntColumn(this->stmt, 14, SQL_WVARCHAR); // sql data type not NULL + CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub + CheckIntColumn(this->stmt, 16, 0); // char octet length + CheckIntColumn(this->stmt, 17, 2); // oridinal position + + CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + + 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 ad1b7cf86e73..c3625b7e27ce 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 @@ -308,6 +308,21 @@ void FlightSQLODBCMockTestBase::CreateTableAllDataType() { )")); } +void FlightSQLODBCMockTestBase::CreateUnicodeTable() { + std::string unicodeSql = arrow::util::WideStringToUTF8( + LR"( + CREATE TABLE 数据( + id INTEGER PRIMARY KEY AUTOINCREMENT, + 资料 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); @@ -423,6 +438,23 @@ void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value) { EXPECT_EQ(std::string(buf, static_cast(bufLen)), value); } +void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& value) { + SQLWCHAR buf[1024]; + SQLLEN bufLen = sizeof(buf) * ODBC::GetSqlWCharSize(); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; + } + + if (bufLen <= 0) + EXPECT_TRUE(value.empty()); + else + EXPECT_EQ(std::wstring(buf), value); +} + void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value) { SQLINTEGER buf; SQLLEN bufLen = sizeof(buf); 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 3859a308ca90..887872570f01 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 @@ -127,6 +127,8 @@ class FlightSQLODBCMockTestBase : public FlightSQLODBCRemoteTestBase { void CreateTestTables(); /// \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; @@ -196,6 +198,12 @@ bool writeDSN(Connection::ConnPropertyMap properties); /// \param[in] value Expected value. void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value); +/// \brief Check wide string column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] value Expected value. +void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& value); + /// \brief Check int column. /// \param[in] stmt Statement. /// \param[in] colId Column ID to check. From a26a50e7e2361710cbebe9273a99cf14c6d714ff Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 17 Jul 2025 17:10:57 -0700 Subject: [PATCH 09/16] Add tests to check all columns --- .../flight/sql/odbc/tests/columns_test.cc | 177 +++++++++++++++--- 1 file changed, 156 insertions(+), 21 deletions(-) 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 fd3c025adf1d..e2fee3f9f803 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -28,22 +28,8 @@ namespace arrow::flight::sql::odbc { -// -AL- TODO: work on fetching all columns. The plan is to use this test as example, -// -AL- next TODO: create table that supports all SQLite data types, -// and change table pattern to match that one table. Use a new test to verify all types. - -// if (boost::iequals(sqlite_type, "int") || boost::iequals(sqlite_type, "integer")) { -// return int64(); -//} else if (boost::iequals(sqlite_type, "REAL")) { -// return float64(); -//} else if (boost::iequals(sqlite_type, "BLOB")) { -// return binary(); -//} else if (boost::iequals(sqlite_type, "TEXT") || boost::iequals(sqlite_type, "DATE") || -// boost::istarts_with(sqlite_type, "char") || -// boost::istarts_with(sqlite_type, "varchar")) { -// return utf8(); -//} -TEST_F(FlightSQLODBCMockTestBase, TestSQLColumns) { +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { + // Check table pattern and column pattern returns all columns this->connect(); // Attempt to get all columns @@ -68,7 +54,7 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumns) { std::string empty = std::string(""); - // 18 columns are returned by SQLColumns + // 18 columns are returned by SQLColumns, check first column thoroughly CheckStringColumn(this->stmt, 1, std::string("main")); // catalog CheckStringColumn(this->stmt, 2, empty); // schema CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name @@ -101,6 +87,73 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumns) { 1); // oridinal position CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + + // Check 2nd Column, only check table and column name + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("foreignName")); // column name + + // Check 3rd Column, only check table and column name + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("value")); // column name + + // Check 4th Column, only check table and column name + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("id")); // column name + + // Check 5th Column, only check table and column name + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("keyName")); // column name + + // Check 6th Column, only check table and column name + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("value")); // column name + + // Check 7th Column, only check table and column name + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + if (ret != SQL_SUCCESS) { + // -AL- temp + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; + } + + CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("foreignId")); // column name + this->disconnect(); } @@ -108,6 +161,8 @@ 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(); @@ -284,10 +339,10 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsUnicode) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 1, std::string("main")); // catalog - CheckStringColumn(this->stmt, 2, empty); // schema - CheckStringColumnW(this->stmt, 3, std::wstring(L"数据")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"资料")); // column name + CheckStringColumn(this->stmt, 1, std::string("main")); // catalog + CheckStringColumn(this->stmt, 2, empty); // schema + CheckStringColumnW(this->stmt, 3, std::wstring(L"数据")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"资料")); // column name CheckIntColumn(this->stmt, 5, SQL_WVARCHAR); // data type @@ -315,4 +370,84 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsUnicode) { 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); + + std::string empty = std::string(""); + + CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("id")); // column name + + // Check 2nd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("id")); // column name + + 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); + + std::string empty = std::string(""); + + CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name + CheckStringColumn(this->stmt, 4, std::string("id")); // column name + + // 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 From 213fb7d031fc3502b8d1b9a8384f52e738b48bb6 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 17 Jul 2025 17:19:34 -0700 Subject: [PATCH 10/16] Clean up comments --- .../flight_sql_statement_get_columns.cc | 1 - .../flight/sql/odbc/tests/columns_test.cc | 32 ------------------- .../flight/sql/odbc/tests/odbc_test_suite.cc | 16 ---------- 3 files changed, 49 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_columns.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_columns.cc index 647e7888e1bc..d3250401193d 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_columns.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_columns.cc @@ -83,7 +83,6 @@ Result> Transform_inner( const std::shared_ptr& original, const optional& column_name_pattern, const MetadataSettings& metadata_settings) { - //-AL- this is where SQLColumn items are retrieved GetColumns_RecordBatchBuilder builder(odbc_version); GetColumns_RecordBatchBuilder::Data data; 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 e2fee3f9f803..dfe68590b2f3 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -40,17 +40,9 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { SQL_NTS, columnPattern, SQL_NTS); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } std::string empty = std::string(""); @@ -91,10 +83,6 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { // Check 2nd Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name CheckStringColumn(this->stmt, 4, std::string("foreignName")); // column name @@ -102,10 +90,6 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { // Check 3rd Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name CheckStringColumn(this->stmt, 4, std::string("value")); // column name @@ -113,10 +97,6 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { // Check 4th Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name CheckStringColumn(this->stmt, 4, std::string("id")); // column name @@ -124,10 +104,6 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { // Check 5th Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name CheckStringColumn(this->stmt, 4, std::string("keyName")); // column name @@ -135,10 +111,6 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { // Check 6th Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name CheckStringColumn(this->stmt, 4, std::string("value")); // column name @@ -146,10 +118,6 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { // Check 7th Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, this->stmt) << std::endl; - } CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name CheckStringColumn(this->stmt, 4, std::string("foreignId")); // column name 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 c3625b7e27ce..a76784276f3c 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 @@ -427,10 +427,6 @@ void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value) { SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_CHAR, buf, sizeof(buf), &bufLen); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } if (bufLen <= 0) EXPECT_TRUE(value.empty()); @@ -444,10 +440,6 @@ void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& value) { SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, sizeof(buf), &bufLen); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } if (bufLen <= 0) EXPECT_TRUE(value.empty()); @@ -461,10 +453,6 @@ void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value) { SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_LONG, &buf, sizeof(buf), &bufLen); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } EXPECT_EQ(buf, value); } @@ -475,10 +463,6 @@ void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& value) { SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_SSHORT, &buf, sizeof(buf), &bufLen); EXPECT_EQ(ret, SQL_SUCCESS); - if (ret != SQL_SUCCESS) { - // -AL- temp - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } EXPECT_EQ(buf, value); } From 7908c205f7695eff27a38c2be251d011e4c7b328 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 17 Jul 2025 17:21:29 -0700 Subject: [PATCH 11/16] Remove SQLTables implementation --- cpp/src/arrow/flight/sql/odbc/entry_points.cc | 13 ++++++-- cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 31 ------------------- cpp/src/arrow/flight/sql/odbc/odbc_api.h | 4 --- .../flight/sql/odbc/tests/odbc_test_suite.cc | 13 -------- .../flight/sql/odbc/tests/odbc_test_suite.h | 1 - 5 files changed, 10 insertions(+), 52 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/entry_points.cc b/cpp/src/arrow/flight/sql/odbc/entry_points.cc index b7f9be6a8068..4b9960f4489f 100644 --- a/cpp/src/arrow/flight/sql/odbc/entry_points.cc +++ b/cpp/src/arrow/flight/sql/odbc/entry_points.cc @@ -288,7 +288,14 @@ SQLRETURN SQL_API SQLTables(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT schemaNameLength, SQLWCHAR* tableName, SQLSMALLINT tableNameLength, SQLWCHAR* tableType, SQLSMALLINT tableTypeLength) { - return arrow::SQLTables(stmt, catalogName, catalogNameLength, schemaName, - schemaNameLength, tableName, tableNameLength, tableType, - tableTypeLength); + LOG_DEBUG( + "SQLTablesW called with stmt: {}, catalogName: {}, catalogNameLength: " + "{}, " + "schemaName: {}, schemaNameLength: {}, tableName: {}, tableNameLength: {}, " + "tableType: {}, " + "tableTypeLength: {}", + stmt, fmt::ptr(catalogName), catalogNameLength, fmt::ptr(schemaName), + schemaNameLength, fmt::ptr(tableName), tableNameLength, fmt::ptr(tableType), + tableTypeLength); + return SQL_ERROR; } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index d567101bce36..168aad2211e0 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -1113,36 +1113,6 @@ SQLRETURN SQLRowCount(SQLHSTMT stmt, SQLLEN* rowCountPtr) { }); } -SQLRETURN SQLTables(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, - SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, - SQLWCHAR* tableName, SQLSMALLINT tableNameLength, SQLWCHAR* tableType, - SQLSMALLINT tableTypeLength) { - LOG_DEBUG( - "SQLTables called with stmt: {}, catalogName: {}, catalogNameLength: " - "{}, " - "schemaName: {}, schemaNameLength: {}, tableName: {}, tableNameLength: {}, " - "tableType: {}, " - "tableTypeLength: {}", - stmt, fmt::ptr(catalogName), catalogNameLength, fmt::ptr(schemaName), - schemaNameLength, fmt::ptr(tableName), tableNameLength, fmt::ptr(tableType), - tableTypeLength); - using namespace ODBC; - - 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 type = SqlWcharToString(tableType, tableTypeLength); - - statement->GetTables(catalogName ? &catalog : nullptr, schemaName ? &schema : nullptr, - tableName ? &table : nullptr, tableType ? &type : nullptr); - - return SQL_SUCCESS; - }); -} - SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, SQLWCHAR* tableName, SQLSMALLINT tableNameLength, @@ -1175,5 +1145,4 @@ SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNa 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 d1ea40cf16f1..b1b5c5a30335 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.h @@ -79,10 +79,6 @@ 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 SQLTables(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, - SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, - SQLWCHAR* tableName, SQLSMALLINT tableNameLength, SQLWCHAR* tableType, - SQLSMALLINT tableTypeLength); SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNameLength, SQLWCHAR* schemaName, SQLSMALLINT schemaNameLength, SQLWCHAR* tableName, SQLSMALLINT tableNameLength, 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 a76784276f3c..cea2bbe2bb32 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 @@ -273,19 +273,6 @@ std::wstring FlightSQLODBCMockTestBase::getQueryAllDataTypes() { return wsql; } -void FlightSQLODBCMockTestBase::CreateTestTables() { - ASSERT_OK(server->ExecuteSql(R"( - CREATE TABLE TestTable ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - keyName varchar(100), - value int); - - INSERT INTO TestTable (keyName, value) VALUES ('One', 1); - INSERT INTO TestTable (keyName, value) VALUES ('Two', 0); - INSERT INTO TestTable (keyName, value) VALUES ('Three', -1); - )")); -} - void FlightSQLODBCMockTestBase::CreateTableAllDataType() { // Limitation on mock SQLite server: // Only int64, float64, binary, and utf8 Arrow Types are supported by 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 887872570f01..25fb0c6d9112 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 @@ -124,7 +124,6 @@ class FlightSQLODBCMockTestBase : public FlightSQLODBCRemoteTestBase { /// \brief Return a SQL query that selects all data types std::wstring getQueryAllDataTypes() override; - void CreateTestTables(); /// \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 From 7def8c5d9125ab5d7c509805877c826197ef9528 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 17 Jul 2025 17:26:07 -0700 Subject: [PATCH 12/16] nit --- cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 dfe68590b2f3..40f57c00b9a9 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -134,7 +134,7 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { this->connect(); this->CreateTableAllDataType(); - // Attempt to get all columns + // Attempt to get all columns from AllTypesTable SQLWCHAR tablePattern[] = L"AllTypesTable"; SQLWCHAR columnPattern[] = L"%"; From feb8f2b331ffe6987763f6cc0050d0422bc009ba Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 18 Jul 2025 10:53:07 -0700 Subject: [PATCH 13/16] Update helper functions for testing Co-Authored-By: rscales <5289449+rscales@users.noreply.github.com> --- .../flight/sql/odbc/tests/odbc_test_suite.cc | 41 ++++++++++++------- .../flight/sql/odbc/tests/odbc_test_suite.h | 16 ++++---- 2 files changed, 35 insertions(+), 22 deletions(-) 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 cea2bbe2bb32..b4889af0e7e0 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,7 +408,7 @@ bool writeDSN(Connection::ConnPropertyMap properties) { return RegisterDsn(config, wDriver.c_str()); } -void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value) { +void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& expected) { char buf[1024]; SQLLEN bufLen = sizeof(buf); @@ -416,42 +416,55 @@ void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value) { EXPECT_EQ(ret, SQL_SUCCESS); if (bufLen <= 0) - EXPECT_TRUE(value.empty()); + EXPECT_TRUE(expected.empty()); else - EXPECT_EQ(std::string(buf, static_cast(bufLen)), value); + EXPECT_EQ(std::string(buf, static_cast(bufLen)), expected); } -void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& value) { +void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected) { SQLWCHAR buf[1024]; - SQLLEN bufLen = sizeof(buf) * ODBC::GetSqlWCharSize(); + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, bufLen, &bufLen); - SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, sizeof(buf), &bufLen); EXPECT_EQ(ret, SQL_SUCCESS); + // TODO REMOVE -AL- + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; + } - if (bufLen <= 0) - EXPECT_TRUE(value.empty()); - else - EXPECT_EQ(std::wstring(buf), value); + if (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); + + std::wcerr << L"Comparing returned string:'" << returned << L"' to expected string:" + << expected << std::endl; + + EXPECT_EQ(returned, expected); + } else { + EXPECT_TRUE(expected.empty()); + } } -void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value) { +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, value); + EXPECT_EQ(buf, expected); } -void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& value) { +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, value); + 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 25fb0c6d9112..3e271bd06e6c 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 @@ -194,24 +194,24 @@ bool writeDSN(Connection::ConnPropertyMap properties); /// \brief Check string column. /// \param[in] stmt Statement. /// \param[in] colId Column ID to check. -/// \param[in] value Expected value. -void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& value); +/// \param[in] expected Expected value. +void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& expected); /// \brief Check wide string column. /// \param[in] stmt Statement. /// \param[in] colId Column ID to check. -/// \param[in] value Expected value. -void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& value); +/// \param[in] expected Expected value. +void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected); /// \brief Check int column. /// \param[in] stmt Statement. /// \param[in] colId Column ID to check. -/// \param[in] value Expected value. -void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& value); +/// \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] value Expected value. -void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& value); +/// \param[in] expected Expected value. +void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& expected); } // namespace arrow::flight::sql::odbc From 49532e786b44a19c9ef84bd72e44fe837a2f233f Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 18 Jul 2025 11:27:40 -0700 Subject: [PATCH 14/16] Add null check for schema mock server doesn't support schema --- .../flight/sql/odbc/tests/columns_test.cc | 138 +++++++++--------- .../flight/sql/odbc/tests/odbc_test_suite.cc | 33 +++-- .../flight/sql/odbc/tests/odbc_test_suite.h | 5 + 3 files changed, 97 insertions(+), 79 deletions(-) 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 40f57c00b9a9..67b720ec77be 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -44,17 +44,17 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::string empty = std::string(""); + std::wstring empty = std::wstring(L""); // 18 columns are returned by SQLColumns, check first column thoroughly - CheckStringColumn(this->stmt, 1, std::string("main")); // catalog - CheckStringColumn(this->stmt, 2, empty); // schema - CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("id")); // column name + CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog + CheckNullColumnW(this->stmt, 2); // schema + CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name CheckIntColumn(this->stmt, 5, SQL_BIGINT); // data type - CheckStringColumn(this->stmt, 6, std::string("BIGINT")); // type name + CheckStringColumnW(this->stmt, 6, std::wstring(L"BIGINT")); // type name // mock limitation: SQLite mock server returns 10 for bigint size when spec indicates // should be 19 @@ -69,8 +69,8 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { CheckSmallIntColumn(this->stmt, 11, SQL_NULLABLE); // nullable - CheckStringColumn(this->stmt, 12, empty); // remarks - CheckStringColumn(this->stmt, 13, empty); // column def + CheckNullColumnW(this->stmt, 12); // remarks + CheckNullColumnW(this->stmt, 13); // column def CheckSmallIntColumn(this->stmt, 14, SQL_BIGINT); // sql data type not NULL CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub @@ -78,49 +78,49 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { CheckIntColumn(this->stmt, 17, 1); // oridinal position - CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable // Check 2nd Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("foreignName")); // column name + CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"foreignName")); // column name // Check 3rd Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("value")); // column name + CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"value")); // column name // Check 4th Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("id")); // column name + CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name // Check 5th Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("keyName")); // column name + CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"keyName")); // column name // Check 6th Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("value")); // column name + CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"value")); // column name // Check 7th Column, only check table and column name ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("foreignId")); // column name + CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"foreignId")); // column name this->disconnect(); } @@ -147,16 +147,16 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::string empty = std::string(""); + std::wstring empty = std::wstring(L""); - CheckStringColumn(this->stmt, 1, std::string("main")); // catalog - CheckStringColumn(this->stmt, 2, empty); // schema - CheckStringColumn(this->stmt, 3, std::string("AllTypesTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("bigint_col")); // column name + CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog + CheckNullColumnW(this->stmt, 2); // schema + CheckStringColumnW(this->stmt, 3, std::wstring(L"AllTypesTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"bigint_col")); // column name CheckIntColumn(this->stmt, 5, SQL_BIGINT); // data type - CheckStringColumn(this->stmt, 6, std::string("BIGINT")); // type name + CheckStringColumnW(this->stmt, 6, std::wstring(L"BIGINT")); // type name // mock limitation: SQLite mock server returns 10 for bigint column size when spec // indicates should be 19 @@ -171,28 +171,28 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { CheckSmallIntColumn(this->stmt, 11, SQL_NULLABLE); // nullable - CheckStringColumn(this->stmt, 12, empty); // remarks - CheckStringColumn(this->stmt, 13, empty); // column def + CheckNullColumnW(this->stmt, 12); // remarks + CheckNullColumnW(this->stmt, 13); // column def CheckSmallIntColumn(this->stmt, 14, SQL_BIGINT); // sql data type not NULL CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub CheckIntColumn(this->stmt, 16, 8); // char octet length CheckIntColumn(this->stmt, 17, 1); // oridinal position - CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable // Check SQLColumn data for 2nd column in AllTypesTable ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 1, std::string("main")); // catalog - CheckStringColumn(this->stmt, 2, empty); // schema - CheckStringColumn(this->stmt, 3, std::string("AllTypesTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("char_col")); // column name + CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog + CheckNullColumnW(this->stmt, 2); // schema + CheckStringColumnW(this->stmt, 3, std::wstring(L"AllTypesTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"char_col")); // column name CheckIntColumn(this->stmt, 5, SQL_WVARCHAR); // data type - CheckStringColumn(this->stmt, 6, std::string("WVARCHAR")); // type name + CheckStringColumnW(this->stmt, 6, std::wstring(L"WVARCHAR")); // type name // mock limitation: SQLite mock server returns 0 for varchar(100) column size when spec // indicates should be 100. @@ -204,28 +204,28 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { CheckSmallIntColumn(this->stmt, 11, SQL_NULLABLE); // nullable - CheckStringColumn(this->stmt, 12, empty); // remarks - CheckStringColumn(this->stmt, 13, empty); // column def + CheckNullColumnW(this->stmt, 12); // remarks + CheckNullColumnW(this->stmt, 13); // column def CheckSmallIntColumn(this->stmt, 14, SQL_WVARCHAR); // sql data type not NULL CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub CheckIntColumn(this->stmt, 16, 0); // char octet length CheckIntColumn(this->stmt, 17, 2); // oridinal position - CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable // Check SQLColumn data for 3rd column in AllTypesTable ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 1, std::string("main")); // catalog - CheckStringColumn(this->stmt, 2, empty); // schema - CheckStringColumn(this->stmt, 3, std::string("AllTypesTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("varbinary_col")); // column name + CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog + CheckNullColumnW(this->stmt, 2); // schema + CheckStringColumnW(this->stmt, 3, std::wstring(L"AllTypesTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"varbinary_col")); // column name CheckIntColumn(this->stmt, 5, SQL_BINARY); // data type - CheckStringColumn(this->stmt, 6, std::string("BINARY")); // type name + CheckStringColumnW(this->stmt, 6, std::wstring(L"BINARY")); // type name // mock limitation: SQLite mock server returns 0 for BLOB column size when spec // indicates should be binary data limit. @@ -237,28 +237,28 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { CheckSmallIntColumn(this->stmt, 11, SQL_NULLABLE); // nullable - CheckStringColumn(this->stmt, 12, empty); // remarks - CheckStringColumn(this->stmt, 13, empty); // column def + CheckNullColumnW(this->stmt, 12); // remarks + CheckNullColumnW(this->stmt, 13); // column def CheckSmallIntColumn(this->stmt, 14, SQL_BINARY); // sql data type not NULL CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub CheckIntColumn(this->stmt, 16, 0); // char octet length CheckIntColumn(this->stmt, 17, 3); // oridinal position - CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable // Check SQLColumn data for 4th column in AllTypesTable ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 1, std::string("main")); // catalog - CheckStringColumn(this->stmt, 2, empty); // schema - CheckStringColumn(this->stmt, 3, std::string("AllTypesTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("double_col")); // column name + CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog + CheckNullColumnW(this->stmt, 2); // schema + CheckStringColumnW(this->stmt, 3, std::wstring(L"AllTypesTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"double_col")); // column name CheckIntColumn(this->stmt, 5, SQL_DOUBLE); // data type - CheckStringColumn(this->stmt, 6, std::string("DOUBLE")); // type name + CheckStringColumnW(this->stmt, 6, std::wstring(L"DOUBLE")); // type name CheckIntColumn(this->stmt, 7, 15); // column size CheckIntColumn(this->stmt, 8, 8); // buffer length @@ -268,15 +268,15 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { CheckSmallIntColumn(this->stmt, 11, SQL_NULLABLE); // nullable - CheckStringColumn(this->stmt, 12, empty); // remarks - CheckStringColumn(this->stmt, 13, empty); // column def + CheckNullColumnW(this->stmt, 12); // remarks + CheckNullColumnW(this->stmt, 13); // column def CheckSmallIntColumn(this->stmt, 14, SQL_DOUBLE); // sql data type not NULL CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub CheckIntColumn(this->stmt, 16, 8); // char octet length CheckIntColumn(this->stmt, 17, 4); // oridinal position - CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable this->disconnect(); } @@ -301,20 +301,20 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsUnicode) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::string empty = std::string(""); + std::wstring empty = std::wstring(L""); // Check SQLColumn data for 2nd column in AllTypesTable ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 1, std::string("main")); // catalog - CheckStringColumn(this->stmt, 2, empty); // schema + CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog + CheckNullColumnW(this->stmt, 2); // schema CheckStringColumnW(this->stmt, 3, std::wstring(L"数据")); // table name CheckStringColumnW(this->stmt, 4, std::wstring(L"资料")); // column name CheckIntColumn(this->stmt, 5, SQL_WVARCHAR); // data type - CheckStringColumn(this->stmt, 6, std::string("WVARCHAR")); // type name + CheckStringColumnW(this->stmt, 6, std::wstring(L"WVARCHAR")); // type name // mock limitation: SQLite mock server returns 0 for varchar(100) column size when spec // indicates should be 100. @@ -326,15 +326,15 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsUnicode) { CheckSmallIntColumn(this->stmt, 11, SQL_NULLABLE); // nullable - CheckStringColumn(this->stmt, 12, empty); // remarks - CheckStringColumn(this->stmt, 13, empty); // column def + CheckNullColumnW(this->stmt, 12); // remarks + CheckNullColumnW(this->stmt, 13); // column def CheckSmallIntColumn(this->stmt, 14, SQL_WVARCHAR); // sql data type not NULL CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub CheckIntColumn(this->stmt, 16, 0); // char octet length CheckIntColumn(this->stmt, 17, 2); // oridinal position - CheckStringColumn(this->stmt, 18, std::string("YES")); // is nullable + CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable this->disconnect(); } @@ -356,17 +356,17 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsColumnPattern) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::string empty = std::string(""); + std::wstring empty = std::wstring(L""); - CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("id")); // column name + CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name // Check 2nd Column ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumn(this->stmt, 3, std::string("intTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("id")); // column name + CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name this->disconnect(); } @@ -388,10 +388,10 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsTableColumnPattern) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::string empty = std::string(""); + std::wstring empty = std::wstring(L""); - CheckStringColumn(this->stmt, 3, std::string("foreignTable")); // table name - CheckStringColumn(this->stmt, 4, std::string("id")); // column name + CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name + CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name // There is no more column ret = SQLFetch(this->stmt); 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 b4889af0e7e0..cf8430667f11 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 @@ -423,7 +423,7 @@ void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& expected) { void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected) { SQLWCHAR buf[1024]; - SQLLEN bufLen = sizeof(buf); + SQLLEN bufLen = sizeof(buf) * ODBC::GetSqlWCharSize(); SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, bufLen, &bufLen); @@ -433,18 +433,31 @@ void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected) std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; } - if (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_GT(bufLen, 0); - std::wcerr << L"Comparing returned string:'" << returned << L"' to expected string:" - << expected << std::endl; + // 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); - } else { - EXPECT_TRUE(expected.empty()); + std::wcerr << L"Comparing returned string:'" << returned << L"' to expected string:" + << expected << std::endl; + + 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); + // TODO REMOVE -AL- + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; } + + EXPECT_EQ(bufLen, SQL_NULL_DATA); } void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& expected) { 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 3e271bd06e6c..d25822cc0674 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 @@ -203,6 +203,11 @@ void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& expected); /// \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. From 4f23a72d41f4fdb3331ac1dd3901e2543794accf Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Fri, 18 Jul 2025 12:07:09 -0700 Subject: [PATCH 15/16] Use 1 function to check all 18 columns in SQLColumns Remove unneeded code --- .../flight/sql/odbc/tests/columns_test.cc | 525 ++++++++++-------- .../flight/sql/odbc/tests/odbc_test_suite.cc | 27 - .../flight/sql/odbc/tests/odbc_test_suite.h | 6 - 3 files changed, 308 insertions(+), 250 deletions(-) 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 67b720ec77be..937ed6a51143 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -27,6 +27,48 @@ #include "gtest/gtest.h" namespace arrow::flight::sql::odbc { +void checkSQLColumns( + 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 + CheckStringColumnW(stmt, 3, expectedTable); // table name + CheckStringColumnW(stmt, 4, expectedColumn); // column name + + CheckIntColumn(stmt, 5, expectedDataType); // data type + + CheckStringColumnW(stmt, 6, expectedTypeName); // type name + + // mock limitation: SQLite mock server returns 10 for bigint size when spec indicates + // should be 19 + CheckIntColumn(stmt, 7, expectedColumnSize); // column size + CheckIntColumn(stmt, 8, expectedBufferLength); // buffer length + + // 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 + 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 not NULL + 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 +} TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { // Check table pattern and column pattern returns all columns @@ -44,83 +86,150 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::wstring empty = std::wstring(L""); - - // 18 columns are returned by SQLColumns, check first column thoroughly - CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog - CheckNullColumnW(this->stmt, 2); // schema - CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name - - CheckIntColumn(this->stmt, 5, SQL_BIGINT); // data type - - CheckStringColumnW(this->stmt, 6, std::wstring(L"BIGINT")); // type name - - // mock limitation: SQLite mock server returns 10 for bigint size when spec indicates - // should be 19 - CheckIntColumn(this->stmt, 7, 10); // column size - CheckIntColumn(this->stmt, 8, 8); // buffer length + checkSQLColumns(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 - // 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 - CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits - CheckSmallIntColumn(this->stmt, 10, 10); // num prec radix - CheckSmallIntColumn(this->stmt, 11, - SQL_NULLABLE); // nullable - - CheckNullColumnW(this->stmt, 12); // remarks - CheckNullColumnW(this->stmt, 13); // column def - - CheckSmallIntColumn(this->stmt, 14, SQL_BIGINT); // sql data type not NULL - CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub - CheckIntColumn(this->stmt, 16, 8); // char octet length - CheckIntColumn(this->stmt, 17, - 1); // oridinal position - - CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable - - // Check 2nd Column, only check table and column name + // Check 2nd Column ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"foreignName")); // column name - - // Check 3rd Column, only check table and column name + checkSQLColumns(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); - CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"value")); // column name - - // Check 4th Column, only check table and column name + checkSQLColumns(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); - CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name - - // Check 5th Column, only check table and column name + checkSQLColumns(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); - CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"keyName")); // column name - - // Check 6th Column, only check table and column name + checkSQLColumns(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); - CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"value")); // column name - - // Check 7th Column, only check table and column name + checkSQLColumns(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); - CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"foreignId")); // column name + checkSQLColumns(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(); } @@ -147,136 +256,93 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::wstring empty = std::wstring(L""); - - CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog - CheckNullColumnW(this->stmt, 2); // schema - CheckStringColumnW(this->stmt, 3, std::wstring(L"AllTypesTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"bigint_col")); // column name - - CheckIntColumn(this->stmt, 5, SQL_BIGINT); // data type - - CheckStringColumnW(this->stmt, 6, std::wstring(L"BIGINT")); // type name - - // mock limitation: SQLite mock server returns 10 for bigint column size when spec - // indicates should be 19 - CheckIntColumn(this->stmt, 7, 10); // column size - CheckIntColumn(this->stmt, 8, 8); // buffer length - - // 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 - CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits - CheckSmallIntColumn(this->stmt, 10, 10); // num prec radix - CheckSmallIntColumn(this->stmt, 11, - SQL_NULLABLE); // nullable - - CheckNullColumnW(this->stmt, 12); // remarks - CheckNullColumnW(this->stmt, 13); // column def - - CheckSmallIntColumn(this->stmt, 14, SQL_BIGINT); // sql data type not NULL - CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub - CheckIntColumn(this->stmt, 16, 8); // char octet length - CheckIntColumn(this->stmt, 17, 1); // oridinal position - - CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable + checkSQLColumns(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); - CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog - CheckNullColumnW(this->stmt, 2); // schema - CheckStringColumnW(this->stmt, 3, std::wstring(L"AllTypesTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"char_col")); // column name - - CheckIntColumn(this->stmt, 5, SQL_WVARCHAR); // data type - - CheckStringColumnW(this->stmt, 6, std::wstring(L"WVARCHAR")); // type name - - // mock limitation: SQLite mock server returns 0 for varchar(100) column size when spec - // indicates should be 100. - CheckIntColumn(this->stmt, 7, 0); // column size - CheckIntColumn(this->stmt, 8, 0); // buffer length - - CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits - CheckSmallIntColumn(this->stmt, 10, 0); // num prec radix - CheckSmallIntColumn(this->stmt, 11, - SQL_NULLABLE); // nullable - - CheckNullColumnW(this->stmt, 12); // remarks - CheckNullColumnW(this->stmt, 13); // column def - - CheckSmallIntColumn(this->stmt, 14, SQL_WVARCHAR); // sql data type not NULL - CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub - CheckIntColumn(this->stmt, 16, 0); // char octet length - CheckIntColumn(this->stmt, 17, 2); // oridinal position - - CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable + checkSQLColumns(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); - CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog - CheckNullColumnW(this->stmt, 2); // schema - CheckStringColumnW(this->stmt, 3, std::wstring(L"AllTypesTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"varbinary_col")); // column name - - CheckIntColumn(this->stmt, 5, SQL_BINARY); // data type - - CheckStringColumnW(this->stmt, 6, std::wstring(L"BINARY")); // type name - - // mock limitation: SQLite mock server returns 0 for BLOB column size when spec - // indicates should be binary data limit. - CheckIntColumn(this->stmt, 7, 0); // column size - CheckIntColumn(this->stmt, 8, 0); // buffer length - - CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits - CheckSmallIntColumn(this->stmt, 10, 0); // num prec radix - CheckSmallIntColumn(this->stmt, 11, - SQL_NULLABLE); // nullable - - CheckNullColumnW(this->stmt, 12); // remarks - CheckNullColumnW(this->stmt, 13); // column def - - CheckSmallIntColumn(this->stmt, 14, SQL_BINARY); // sql data type not NULL - CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub - CheckIntColumn(this->stmt, 16, 0); // char octet length - CheckIntColumn(this->stmt, 17, 3); // oridinal position - - CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable + checkSQLColumns(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); - CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog - CheckNullColumnW(this->stmt, 2); // schema - CheckStringColumnW(this->stmt, 3, std::wstring(L"AllTypesTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"double_col")); // column name - - CheckIntColumn(this->stmt, 5, SQL_DOUBLE); // data type - - CheckStringColumnW(this->stmt, 6, std::wstring(L"DOUBLE")); // type name - - CheckIntColumn(this->stmt, 7, 15); // column size - CheckIntColumn(this->stmt, 8, 8); // buffer length - - CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits - CheckSmallIntColumn(this->stmt, 10, 2); // num prec radix - CheckSmallIntColumn(this->stmt, 11, - SQL_NULLABLE); // nullable - - CheckNullColumnW(this->stmt, 12); // remarks - CheckNullColumnW(this->stmt, 13); // column def - - CheckSmallIntColumn(this->stmt, 14, SQL_DOUBLE); // sql data type not NULL - CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub - CheckIntColumn(this->stmt, 16, 8); // char octet length - CheckIntColumn(this->stmt, 17, 4); // oridinal position - - CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable + checkSQLColumns(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(); } @@ -297,44 +363,31 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsUnicode) { EXPECT_EQ(ret, SQL_SUCCESS); - // Skip the check for 1st column in unicode table 数据 + // Check SQLColumn data for 1st column ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::wstring empty = std::wstring(L""); - - // Check SQLColumn data for 2nd column in AllTypesTable + checkSQLColumns(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_SUCCESS); - - CheckStringColumnW(this->stmt, 1, std::wstring(L"main")); // catalog - CheckNullColumnW(this->stmt, 2); // schema - CheckStringColumnW(this->stmt, 3, std::wstring(L"数据")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"资料")); // column name - - CheckIntColumn(this->stmt, 5, SQL_WVARCHAR); // data type - - CheckStringColumnW(this->stmt, 6, std::wstring(L"WVARCHAR")); // type name - - // mock limitation: SQLite mock server returns 0 for varchar(100) column size when spec - // indicates should be 100. - CheckIntColumn(this->stmt, 7, 0); // column size - CheckIntColumn(this->stmt, 8, 0); // buffer length - - CheckSmallIntColumn(this->stmt, 9, 15); // decimal digits - CheckSmallIntColumn(this->stmt, 10, 0); // num prec radix - CheckSmallIntColumn(this->stmt, 11, - SQL_NULLABLE); // nullable - - CheckNullColumnW(this->stmt, 12); // remarks - CheckNullColumnW(this->stmt, 13); // column def - - CheckSmallIntColumn(this->stmt, 14, SQL_WVARCHAR); // sql data type not NULL - CheckSmallIntColumn(this->stmt, 15, NULL); // sql date type sub - CheckIntColumn(this->stmt, 16, 0); // char octet length - CheckIntColumn(this->stmt, 17, 2); // oridinal position - - CheckStringColumnW(this->stmt, 18, std::wstring(L"YES")); // is nullable + EXPECT_EQ(ret, SQL_NO_DATA); this->disconnect(); } @@ -356,17 +409,43 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsColumnPattern) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::wstring empty = std::wstring(L""); - - CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name + checkSQLColumns(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); - CheckStringColumnW(this->stmt, 3, std::wstring(L"intTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name + checkSQLColumns(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 this->disconnect(); } @@ -388,10 +467,22 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsTableColumnPattern) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - std::wstring empty = std::wstring(L""); - - CheckStringColumnW(this->stmt, 3, std::wstring(L"foreignTable")); // table name - CheckStringColumnW(this->stmt, 4, std::wstring(L"id")); // column name + checkSQLColumns(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); 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 cf8430667f11..7bb85b5d3244 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 @@ -299,7 +299,6 @@ void FlightSQLODBCMockTestBase::CreateUnicodeTable() { std::string unicodeSql = arrow::util::WideStringToUTF8( LR"( CREATE TABLE 数据( - id INTEGER PRIMARY KEY AUTOINCREMENT, 资料 varchar(100)); INSERT INTO 数据 (资料) VALUES ('第一行'); @@ -408,30 +407,12 @@ bool writeDSN(Connection::ConnPropertyMap properties) { return RegisterDsn(config, wDriver.c_str()); } -void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& expected) { - char buf[1024]; - SQLLEN bufLen = sizeof(buf); - - SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_CHAR, buf, sizeof(buf), &bufLen); - EXPECT_EQ(ret, SQL_SUCCESS); - - if (bufLen <= 0) - EXPECT_TRUE(expected.empty()); - else - EXPECT_EQ(std::string(buf, static_cast(bufLen)), expected); -} - 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); - // TODO REMOVE -AL- - if (ret != SQL_SUCCESS) { - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } EXPECT_GT(bufLen, 0); @@ -439,9 +420,6 @@ void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected) size_t charCount = static_cast(bufLen) / ODBC::GetSqlWCharSize(); std::wstring returned(buf, buf + charCount); - std::wcerr << L"Comparing returned string:'" << returned << L"' to expected string:" - << expected << std::endl; - EXPECT_EQ(returned, expected); } @@ -450,12 +428,7 @@ void CheckNullColumnW(SQLHSTMT stmt, int colId) { SQLLEN bufLen = sizeof(buf); SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, bufLen, &bufLen); - EXPECT_EQ(ret, SQL_SUCCESS); - // TODO REMOVE -AL- - if (ret != SQL_SUCCESS) { - std::cerr << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl; - } EXPECT_EQ(bufLen, SQL_NULL_DATA); } 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 d25822cc0674..9d337f45c3de 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 @@ -191,12 +191,6 @@ bool writeDSN(std::string connection_str); /// \return true on success bool writeDSN(Connection::ConnPropertyMap properties); -/// \brief Check string column. -/// \param[in] stmt Statement. -/// \param[in] colId Column ID to check. -/// \param[in] expected Expected value. -void CheckStringColumn(SQLHSTMT stmt, int colId, const std::string& expected); - /// \brief Check wide string column. /// \param[in] stmt Statement. /// \param[in] colId Column ID to check. From e012e5005c89a4b3ca5f57bc89ec9886f1b8df79 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 21 Jul 2025 10:23:56 -0700 Subject: [PATCH 16/16] Address comments from James - Test different data type return value for SQLColumns - Add todo comment for SQLDescribeCol tests --- cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 2 + .../flight/sql/odbc/tests/columns_test.cc | 1002 ++++++++++++----- .../flight/sql/odbc/tests/odbc_test_suite.cc | 9 +- .../flight/sql/odbc/tests/odbc_test_suite.h | 7 +- 4 files changed, 748 insertions(+), 272 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index 168aad2211e0..5553db102761 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -1117,6 +1117,8 @@ SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, SQLSMALLINT catalogNa 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: " "{}, " 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 937ed6a51143..a05b8cd0f3a8 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -26,33 +26,28 @@ #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& 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 - CheckStringColumnW(stmt, 3, expectedTable); // table name - CheckStringColumnW(stmt, 4, expectedColumn); // column name + 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 - // mock limitation: SQLite mock server returns 10 for bigint size when spec indicates - // should be 19 CheckIntColumn(stmt, 7, expectedColumnSize); // column size CheckIntColumn(stmt, 8, expectedBufferLength); // buffer length - // 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 CheckSmallIntColumn(stmt, 9, expectedDecimalDigits); // decimal digits CheckSmallIntColumn(stmt, 10, expectedNumPrecRadix); // num prec radix CheckSmallIntColumn(stmt, 11, @@ -61,7 +56,7 @@ void checkSQLColumns( CheckNullColumnW(stmt, 12); // remarks CheckNullColumnW(stmt, 13); // column def - CheckSmallIntColumn(stmt, 14, expectedSqlDataType); // sql data type not NULL + 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, @@ -70,6 +65,43 @@ void checkSQLColumns( 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(); @@ -86,150 +118,155 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - checkSQLColumns(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 + // 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); - checkSQLColumns(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 + 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); - checkSQLColumns(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 + 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); - checkSQLColumns(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 + 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); - checkSQLColumns(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 + 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); - checkSQLColumns(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 + 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); - checkSQLColumns(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 + 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(); } @@ -256,89 +293,89 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - checkSQLColumns(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 + 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); - checkSQLColumns(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 + 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); - checkSQLColumns(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 + 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); - checkSQLColumns(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 + 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); @@ -367,23 +404,23 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsUnicode) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - checkSQLColumns(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 + 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); @@ -392,6 +429,437 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsUnicode) { 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 @@ -409,43 +877,47 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsColumnPattern) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - checkSQLColumns(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 + 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); - checkSQLColumns(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 + 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(); } @@ -467,22 +939,22 @@ TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsTableColumnPattern) { ret = SQLFetch(this->stmt); EXPECT_EQ(ret, SQL_SUCCESS); - checkSQLColumns(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 + 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); 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 7bb85b5d3244..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); } 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 9d337f45c3de..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