diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 106cc1fd86ef..b29b55d55867 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -476,6 +476,10 @@ jobs: PIPX_BASE_PYTHON: ${{ steps.python-install.outputs.python-path }} run: | ci/scripts/install_gcs_testbench.sh default + - name: Register Flight SQL ODBC Driver + shell: cmd + run: | + call "cpp\src\arrow\flight\sql\odbc\install\install_amd64.cmd" ${{github.workspace}}\build\cpp\%ARROW_BUILD_TYPE%\libarrow_flight_sql_odbc.dll - name: Test shell: msys2 {0} run: | diff --git a/.gitignore b/.gitignore index 52ffa6c6124c..11b1a616c51c 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ dependency-reduced-pom.xml MANIFEST compile_commands.json build.ninja +build*/ # Generated Visual Studio files *.vcxproj diff --git a/ci/scripts/cpp_build.sh b/ci/scripts/cpp_build.sh index ec697303d86d..2d0a0f899751 100755 --- a/ci/scripts/cpp_build.sh +++ b/ci/scripts/cpp_build.sh @@ -64,6 +64,7 @@ if [ "${ARROW_ENABLE_THREADING:-ON}" = "OFF" ]; then ARROW_AZURE=OFF ARROW_FLIGHT=OFF ARROW_FLIGHT_SQL=OFF + ARROW_FLIGHT_SQL_ODBC=OFF ARROW_GCS=OFF ARROW_JEMALLOC=OFF ARROW_MIMALLOC=OFF @@ -206,6 +207,7 @@ else -DARROW_FILESYSTEM=${ARROW_FILESYSTEM:-ON} \ -DARROW_FLIGHT=${ARROW_FLIGHT:-OFF} \ -DARROW_FLIGHT_SQL=${ARROW_FLIGHT_SQL:-OFF} \ + -DARROW_FLIGHT_SQL_ODBC=${ARROW_FLIGHT_SQL_ODBC:-OFF} \ -DARROW_FUZZING=${ARROW_FUZZING:-OFF} \ -DARROW_GANDIVA_PC_CXX_FLAGS=${ARROW_GANDIVA_PC_CXX_FLAGS:-} \ -DARROW_GANDIVA=${ARROW_GANDIVA:-OFF} \ diff --git a/cpp/cmake_modules/DefineOptions.cmake b/cpp/cmake_modules/DefineOptions.cmake index ca2a7eb763ed..808170364819 100644 --- a/cpp/cmake_modules/DefineOptions.cmake +++ b/cpp/cmake_modules/DefineOptions.cmake @@ -108,7 +108,7 @@ endmacro() macro(resolve_option_dependencies) # Arrow Flight SQL ODBC is available only for Windows for now. - if(NOT MSVC_TOOLCHAIN) + if(NOT WIN32) set(ARROW_FLIGHT_SQL_ODBC OFF) endif() if(MSVC_TOOLCHAIN) diff --git a/cpp/cmake_modules/ThirdpartyToolchain.cmake b/cpp/cmake_modules/ThirdpartyToolchain.cmake index 5264beed7839..69ca6a69df5a 100644 --- a/cpp/cmake_modules/ThirdpartyToolchain.cmake +++ b/cpp/cmake_modules/ThirdpartyToolchain.cmake @@ -1287,7 +1287,7 @@ if(ARROW_USE_BOOST) endif() if(ARROW_BOOST_REQUIRE_LIBRARY) set(ARROW_BOOST_COMPONENTS filesystem system) - if(ARROW_FLIGHT_SQL_ODBC AND MSVC) + if(ARROW_FLIGHT_SQL_ODBC) list(APPEND ARROW_BOOST_COMPONENTS locale) endif() set(ARROW_BOOST_OPTIONAL_COMPONENTS process) diff --git a/cpp/src/arrow/flight/CMakeLists.txt b/cpp/src/arrow/flight/CMakeLists.txt index 663e7e241019..68d51da17dcd 100644 --- a/cpp/src/arrow/flight/CMakeLists.txt +++ b/cpp/src/arrow/flight/CMakeLists.txt @@ -291,7 +291,9 @@ if(ARROW_TESTING) STATIC_INSTALL_INTERFACE_LIBS ${ARROW_FLIGHT_TESTING_STATIC_INSTALL_INTERFACE_LIBS} PRIVATE_INCLUDES - "${Protobuf_INCLUDE_DIRS}") + "${Protobuf_INCLUDE_DIRS}" + SHARED_PRIVATE_LINK_LIBS + GTest::gmock) foreach(LIB_TARGET ${ARROW_FLIGHT_TESTING_LIBRARIES}) target_compile_definitions(${LIB_TARGET} PRIVATE ARROW_FLIGHT_EXPORTING) diff --git a/cpp/src/arrow/flight/sql/odbc/ArrowFlightSqlOdbcConfig.cmake.in b/cpp/src/arrow/flight/sql/odbc/ArrowFlightSqlOdbcConfig.cmake.in new file mode 100644 index 000000000000..da6d44ebc823 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/ArrowFlightSqlOdbcConfig.cmake.in @@ -0,0 +1,38 @@ +# 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. +# +# This config sets the following variables in your project:: +# +# ArrowFlightSqlOdbc_FOUND - true if Arrow Flight SQL ODBC found on the system +# +# This config sets the following targets in your project:: +# +# ArrowFlightSqlOdbc::arrow_flight_sql_odbc_shared - for linked as shared library if shared library is built +# ArrowFlightSqlOdbc::arrow_flight_sql_odbc_static - for linked as static library if static library is built + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(ArrowFlightSql) + +include("${CMAKE_CURRENT_LIST_DIR}/ArrowFlightSqlOdbcTargets.cmake") + +arrow_keep_backward_compatibility(ArrowFlightSqlOdbc arrow_flight_sql_odbc) + +check_required_components(ArrowFlightSqlOdbc) + +arrow_show_details(ArrowFlightSqlOdbc ARROW_FLIGHT_SQL_ODBC) diff --git a/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt index 80be0dee99f1..29495eb0fe3e 100644 --- a/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt @@ -15,7 +15,79 @@ # specific language governing permissions and limitations # under the License. +# Use C++ 20 for ODBC and its subdirectory +# GH-44792: Arrow will switch to C++ 20 +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + add_custom_target(arrow_flight_sql_odbc) +# Ensure fmt is loaded as header only +add_compile_definitions(FMT_HEADER_ONLY) + +if(WIN32) + if(MSVC_VERSION GREATER_EQUAL 1900) + set(ODBCINST legacy_stdio_definitions odbccp32 shlwapi) + elseif(MINGW) + set(ODBCINST odbccp32 shlwapi) + endif() +elseif(APPLE) + set(ODBCINST iodbcinst) +else() + set(ODBCINST odbcinst) +endif() + +add_definitions(-DUNICODE=1) + +include(FetchContent) +fetchcontent_declare(spdlog + URL https://github.com/gabime/spdlog/archive/refs/tags/v1.15.3.zip + CONFIGURE_COMMAND + "" + BUILD_COMMAND + "") +fetchcontent_makeavailable(spdlog) + add_subdirectory(flight_sql) add_subdirectory(odbcabstraction) +add_subdirectory(tests) + +arrow_install_all_headers("arrow/flight/sql/odbc") + +set(ARROW_FLIGHT_SQL_ODBC_SRCS entry_points.cc odbc_api.cc) + +if(WIN32) + list(APPEND ARROW_FLIGHT_SQL_ODBC_SRCS odbc.def) +endif() + +add_arrow_lib(arrow_flight_sql_odbc + CMAKE_PACKAGE_NAME + ArrowFlightSqlOdbc + PKG_CONFIG_NAME + arrow-flight-sql-odbc + OUTPUTS + ARROW_FLIGHT_SQL_ODBC_LIBRARIES + SOURCES + ${ARROW_FLIGHT_SQL_ODBC_SRCS} + DEPENDENCIES + arrow_flight_sql + SHARED_LINK_FLAGS + ${ARROW_VERSION_SCRIPT_FLAGS} # Defined in cpp/arrow/CMakeLists.txt + SHARED_LINK_LIBS + arrow_flight_sql_shared + SHARED_INSTALL_INTERFACE_LIBS + ArrowFlight::arrow_flight_sql_shared + STATIC_LINK_LIBS + arrow_flight_sql_static + STATIC_INSTALL_INTERFACE_LIBS + ArrowFlight::arrow_flight_sql_static + SHARED_PRIVATE_LINK_LIBS + ${ODBC_LIBRARIES} + ${ODBCINST} + odbcabstraction + arrow_odbc_spi_impl + spdlog::spdlog) + +foreach(LIB_TARGET ${ARROW_FLIGHT_SQL_ODBC_LIBRARIES}) + target_compile_definitions(${LIB_TARGET} PRIVATE ARROW_FLIGHT_SQL_ODBC_EXPORTING) +endforeach() diff --git a/cpp/src/arrow/flight/sql/odbc/README b/cpp/src/arrow/flight/sql/odbc/README new file mode 100644 index 000000000000..04749d9b859b --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/README @@ -0,0 +1,19 @@ +Steps to Register the 64-bit Apache Arrow ODBC driver on Windows + +After the build succeeds, the ODBC DLL will be located in +`build\debug\Debug` for a debug build and `build\release\Release` for a release build. + +1. Open Power Shell as administrator. + +2. Register your ODBC DLL: + Need to replace with actual path to repository in the commands. + + i. `cd to repo.` + ii. `cd ` + iii. Run script to register your ODBC DLL as Apache Arrow Flight SQL ODBC Driver + `.\cpp\src\arrow\flight\sql\odbc\install\install_amd64.cmd \cpp\build\< release | debug >\< Release | Debug>\arrow_flight_sql_odbc.dll` + Example command for reference: + `.\cpp\src\arrow\flight\sql\odbc\install\install_amd64.cmd C:\path\to\arrow\cpp\build\release\Release\arrow_flight_sql_odbc.dll` + +If the registration is successful, then Apache Arrow Flight SQL ODBC Driver +should show as an available ODBC driver in the x64 ODBC Driver Manager. diff --git a/cpp/src/arrow/flight/sql/odbc/arrow-flight-sql-odbc.pc.in b/cpp/src/arrow/flight/sql/odbc/arrow-flight-sql-odbc.pc.in new file mode 100644 index 000000000000..789590349541 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/arrow-flight-sql-odbc.pc.in @@ -0,0 +1,27 @@ +# 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. + +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=@ARROW_PKG_CONFIG_INCLUDEDIR@ +libdir=@ARROW_PKG_CONFIG_LIBDIR@ + +Name: Apache Arrow Flight SQL ODBC +Description: Apache Arrow Flight SQL ODBC extension +Version: @ARROW_VERSION@ +Requires: arrow-flight-sql +Libs: -L${libdir} -larrow_flight_sql_odbc +Cflags.private: -DARROW_FLIGHT_SQL_ODBC_STATIC diff --git a/cpp/src/arrow/flight/sql/odbc/entry_points.cc b/cpp/src/arrow/flight/sql/odbc/entry_points.cc new file mode 100644 index 000000000000..cfbe0fa25d89 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/entry_points.cc @@ -0,0 +1,304 @@ +// 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. + +// platform.h includes windows.h, so it needs to be included first +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include +#include + +#include "arrow/flight/sql/odbc/odbc_api.h" +#include "arrow/flight/sql/odbc/visibility.h" + +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_environment.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h" + +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/logger.h" + +SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT type, SQLHANDLE parent, SQLHANDLE* result) { + return arrow::SQLAllocHandle(type, parent, result); +} + +SQLRETURN SQL_API SQLAllocEnv(SQLHENV* env) { + return arrow::SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, env); +} + +SQLRETURN SQL_API SQLAllocConnect(SQLHENV env, SQLHDBC* conn) { + return arrow::SQLAllocHandle(SQL_HANDLE_DBC, env, conn); +} + +SQLRETURN SQL_API SQLAllocStmt(SQLHDBC conn, SQLHSTMT* stmt) { + return arrow::SQLAllocHandle(SQL_HANDLE_STMT, conn, stmt); +} + +SQLRETURN SQL_API SQLFreeHandle(SQLSMALLINT type, SQLHANDLE handle) { + return arrow::SQLFreeHandle(type, handle); +} + +SQLRETURN SQL_API SQLFreeEnv(SQLHENV env) { + return arrow::SQLFreeHandle(SQL_HANDLE_ENV, env); +} + +SQLRETURN SQL_API SQLFreeConnect(SQLHDBC conn) { + return arrow::SQLFreeHandle(SQL_HANDLE_DBC, conn); +} + +SQLRETURN SQL_API SQLFreeStmt(SQLHSTMT stmt, SQLUSMALLINT option) { + return arrow::SQLFreeStmt(stmt, option); +} + +SQLRETURN SQL_API SQLGetDiagField(SQLSMALLINT handleType, SQLHANDLE handle, + SQLSMALLINT recNumber, SQLSMALLINT diagIdentifier, + SQLPOINTER diagInfoPtr, SQLSMALLINT bufferLength, + SQLSMALLINT* stringLengthPtr) { + return arrow::SQLGetDiagField(handleType, handle, recNumber, diagIdentifier, + diagInfoPtr, bufferLength, stringLengthPtr); +} + +SQLRETURN SQL_API SQLGetDiagRec(SQLSMALLINT handleType, SQLHANDLE handle, + SQLSMALLINT recNumber, SQLWCHAR* sqlState, + SQLINTEGER* nativeErrorPtr, SQLWCHAR* messageText, + SQLSMALLINT bufferLength, SQLSMALLINT* textLengthPtr) { + return arrow::SQLGetDiagRec(handleType, handle, recNumber, sqlState, nativeErrorPtr, + messageText, bufferLength, textLengthPtr); +} + +SQLRETURN SQL_API SQLGetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER valuePtr, + SQLINTEGER bufferLen, SQLINTEGER* strLenPtr) { + return arrow::SQLGetEnvAttr(env, attr, valuePtr, bufferLen, strLenPtr); +} + +SQLRETURN SQL_API SQLSetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER valuePtr, + SQLINTEGER strLen) { + return arrow::SQLSetEnvAttr(env, attr, valuePtr, strLen); +} + +SQLRETURN SQL_API SQLGetConnectAttr(SQLHDBC conn, SQLINTEGER attribute, + SQLPOINTER valuePtr, SQLINTEGER bufferLength, + SQLINTEGER* stringLengthPtr) { + return arrow::SQLGetConnectAttr(conn, attribute, valuePtr, bufferLength, + stringLengthPtr); +} + +SQLRETURN SQL_API SQLSetConnectAttr(SQLHDBC conn, SQLINTEGER attr, SQLPOINTER value, + SQLINTEGER valueLen) { + return arrow::SQLSetConnectAttr(conn, attr, value, valueLen); +} + +SQLRETURN SQL_API SQLGetInfo(SQLHDBC conn, SQLUSMALLINT infoType, SQLPOINTER infoValuePtr, + SQLSMALLINT bufLen, SQLSMALLINT* length) { + return arrow::SQLGetInfo(conn, infoType, infoValuePtr, bufLen, length); +} + +SQLRETURN SQL_API SQLDriverConnect(SQLHDBC conn, SQLHWND windowHandle, + SQLWCHAR* inConnectionString, + SQLSMALLINT inConnectionStringLen, + SQLWCHAR* outConnectionString, + SQLSMALLINT outConnectionStringBufferLen, + SQLSMALLINT* outConnectionStringLen, + SQLUSMALLINT driverCompletion) { + return arrow::SQLDriverConnect( + conn, windowHandle, inConnectionString, inConnectionStringLen, outConnectionString, + outConnectionStringBufferLen, outConnectionStringLen, driverCompletion); +} + +SQLRETURN SQL_API SQLConnect(SQLHDBC conn, SQLWCHAR* dsnName, SQLSMALLINT dsnNameLen, + SQLWCHAR* userName, SQLSMALLINT userNameLen, + SQLWCHAR* password, SQLSMALLINT passwordLen) { + return arrow::SQLConnect(conn, dsnName, dsnNameLen, userName, userNameLen, password, + passwordLen); +} + +SQLRETURN SQL_API SQLDisconnect(SQLHDBC conn) { return arrow::SQLDisconnect(conn); } + +SQLRETURN SQL_API SQLGetStmtAttr(SQLHSTMT stmt, SQLINTEGER attribute, SQLPOINTER valuePtr, + SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr) { + return arrow::SQLGetStmtAttr(stmt, attribute, valuePtr, bufferLength, stringLengthPtr); +} + +SQLRETURN SQL_API SQLExecDirect(SQLHSTMT stmt, SQLWCHAR* queryText, + SQLINTEGER textLength) { + return arrow::SQLExecDirect(stmt, queryText, textLength); +} + +SQLRETURN SQL_API SQLFetch(SQLHSTMT stmt) { return arrow::SQLFetch(stmt); } + +SQLRETURN SQL_API SQLExtendedFetch(SQLHSTMT stmt, SQLUSMALLINT fetchOrientation, + SQLLEN fetchOffset, SQLULEN* rowCountPtr, + SQLUSMALLINT* rowStatusArray) { + return arrow::SQLExtendedFetch(stmt, fetchOrientation, fetchOffset, rowCountPtr, + rowStatusArray); +} + +SQLRETURN SQL_API SQLFetchScroll(SQLHSTMT stmt, SQLSMALLINT fetchOrientation, + SQLLEN fetchOffset) { + return arrow::SQLFetchScroll(stmt, fetchOrientation, fetchOffset); +} + +SQLRETURN SQL_API SQLGetData(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, + SQLLEN* indicatorPtr) { + return arrow::SQLGetData(stmt, recordNumber, cType, dataPtr, bufferLength, + indicatorPtr); +} + +SQLRETURN SQL_API SQLPrepare(SQLHSTMT stmt, SQLWCHAR* queryText, SQLINTEGER textLength) { + return arrow::SQLPrepare(stmt, queryText, textLength); +} + +SQLRETURN SQL_API SQLExecute(SQLHSTMT stmt) { return arrow::SQLExecute(stmt); } + +SQLRETURN SQL_API SQLBindCol(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, + SQLLEN* indicatorPtr) { + return arrow::SQLBindCol(stmt, recordNumber, cType, dataPtr, bufferLength, + indicatorPtr); +} + +SQLRETURN SQL_API SQLCancel(SQLHSTMT stmt) { + LOG_DEBUG("SQLCancel called with stmt: {}", stmt); + return ODBC::ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + throw driver::odbcabstraction::DriverException("SQLCancel is not implemented", + "IM001"); + return SQL_ERROR; + }); +} + +SQLRETURN SQL_API SQLCloseCursor(SQLHSTMT stmt) { return arrow::SQLCloseCursor(stmt); } + +SQLRETURN SQL_API SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT recordNumber, + SQLUSMALLINT fieldIdentifier, + SQLPOINTER characterAttributePtr, + SQLSMALLINT bufferLength, SQLSMALLINT* outputLength, + SQLLEN* numericAttributePtr) { + return arrow::SQLColAttribute(stmt, recordNumber, fieldIdentifier, + characterAttributePtr, bufferLength, outputLength, + numericAttributePtr); +} + +SQLRETURN SQL_API SQLTables(SQLHSTMT stmt, SQLWCHAR* catalogName, + SQLSMALLINT catalogNameLength, SQLWCHAR* schemaName, + SQLSMALLINT schemaNameLength, SQLWCHAR* tableName, + SQLSMALLINT tableNameLength, SQLWCHAR* tableType, + SQLSMALLINT tableTypeLength) { + return arrow::SQLTables(stmt, catalogName, catalogNameLength, schemaName, + schemaNameLength, tableName, tableNameLength, tableType, + tableTypeLength); +} + +SQLRETURN SQL_API SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalogName, + SQLSMALLINT catalogNameLength, SQLWCHAR* schemaName, + SQLSMALLINT schemaNameLength, SQLWCHAR* tableName, + SQLSMALLINT tableNameLength, SQLWCHAR* columnName, + SQLSMALLINT columnNameLength) { + return arrow::SQLColumns(stmt, catalogName, catalogNameLength, schemaName, + schemaNameLength, tableName, tableNameLength, columnName, + columnNameLength); +} + +SQLRETURN SQL_API SQLForeignKeys(SQLHSTMT stmt, SQLWCHAR* pKCatalogName, + SQLSMALLINT pKCatalogNameLength, SQLWCHAR* pKSchemaName, + SQLSMALLINT pKSchemaNameLength, SQLWCHAR* pKTableName, + SQLSMALLINT pKTableNameLength, SQLWCHAR* fKCatalogName, + SQLSMALLINT fKCatalogNameLength, SQLWCHAR* fKSchemaName, + SQLSMALLINT fKSchemaNameLength, SQLWCHAR* fKTableName, + SQLSMALLINT fKTableNameLength) { + LOG_DEBUG( + "SQLForeignKeysW called with stmt: {}, pKCatalogName: {}, " + "pKCatalogNameLength: " + "{}, pKSchemaName: {}, pKSchemaNameLength: {}, pKTableName: {}, pKTableNameLength: " + "{}, " + "fKCatalogName: {}, fKCatalogNameLength: {}, fKSchemaName: {}, fKSchemaNameLength: " + "{}, " + "fKTableName: {}, fKTableNameLength : {}", + stmt, fmt::ptr(pKCatalogName), pKCatalogNameLength, fmt::ptr(pKSchemaName), + pKSchemaNameLength, fmt::ptr(pKTableName), pKTableNameLength, + fmt::ptr(fKCatalogName), fKCatalogNameLength, fmt::ptr(fKSchemaName), + fKSchemaNameLength, fmt::ptr(fKTableName), fKTableNameLength); + return ODBC::ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + throw driver::odbcabstraction::DriverException("SQLForeignKeysW is not implemented", + "IM001"); + return SQL_ERROR; + }); +} + +SQLRETURN SQL_API SQLGetTypeInfo(SQLHSTMT stmt, SQLSMALLINT dataType) { + LOG_DEBUG("SQLGetTypeInfoW called with stmt: {} dataType: {}", stmt, dataType); + return ODBC::ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + throw driver::odbcabstraction::DriverException("SQLGetTypeInfoW is not implemented", + "IM001"); + return SQL_ERROR; + }); +} + +SQLRETURN SQL_API SQLMoreResults(SQLHSTMT stmt) { return arrow::SQLMoreResults(stmt); } + +SQLRETURN SQL_API SQLNativeSql(SQLHDBC connectionHandle, SQLWCHAR* inStatementText, + SQLINTEGER inStatementTextLength, + SQLWCHAR* outStatementText, SQLINTEGER bufferLength, + SQLINTEGER* outStatementTextLength) { + return arrow::SQLNativeSql(connectionHandle, inStatementText, inStatementTextLength, + outStatementText, bufferLength, outStatementTextLength); +} + +SQLRETURN SQL_API SQLNumResultCols(SQLHSTMT stmt, SQLSMALLINT* columnCountPtr) { + return arrow::SQLNumResultCols(stmt, columnCountPtr); +} + +SQLRETURN SQL_API SQLRowCount(SQLHSTMT stmt, SQLLEN* rowCountPtr) { + return arrow::SQLRowCount(stmt, rowCountPtr); +} + +SQLRETURN SQL_API SQLPrimaryKeys(SQLHSTMT stmt, SQLWCHAR* catalogName, + SQLSMALLINT catalogNameLength, SQLWCHAR* schemaName, + SQLSMALLINT schemaNameLength, SQLWCHAR* tableName, + SQLSMALLINT tableNameLength) { + LOG_DEBUG( + "SQLPrimaryKeysW called with stmt: {}, catalogName: {}, " + "catalogNameLength: " + "{}, schemaName: {}, schemaNameLength: {}, tableName: {}, tableNameLength: {}", + stmt, fmt::ptr(catalogName), catalogNameLength, fmt::ptr(schemaName), + schemaNameLength, fmt::ptr(tableName), tableNameLength); + return ODBC::ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + throw driver::odbcabstraction::DriverException("SQLPrimaryKeysW is not implemented", + "IM001"); + return SQL_ERROR; + }); +} + +SQLRETURN SQL_API SQLSetStmtAttr(SQLHSTMT stmt, SQLINTEGER attribute, SQLPOINTER valuePtr, + SQLINTEGER stringLength) { + return arrow::SQLSetStmtAttr(stmt, attribute, valuePtr, stringLength); +} + +SQLRETURN SQL_API SQLDescribeCol(SQLHSTMT statementHandle, SQLUSMALLINT columnNumber, + SQLWCHAR* columnName, SQLSMALLINT bufferLength, + SQLSMALLINT* nameLengthPtr, SQLSMALLINT* dataTypePtr, + SQLULEN* columnSizePtr, SQLSMALLINT* decimalDigitsPtr, + SQLSMALLINT* nullablePtr) { + return arrow::SQLDescribeCol(statementHandle, columnNumber, columnName, bufferLength, + nameLengthPtr, dataTypePtr, columnSizePtr, + decimalDigitsPtr, nullablePtr); +} diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/flight_sql/CMakeLists.txt index 56aabb54dbf6..bd876804279c 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/CMakeLists.txt @@ -76,6 +76,8 @@ add_library(arrow_odbc_spi_impl scalar_function_reporter.h system_trust_store.cc system_trust_store.h + system_dsn.cc + system_dsn.h utils.cc) target_include_directories(arrow_odbc_spi_impl PUBLIC include include/flight_sql @@ -96,13 +98,15 @@ if(WIN32) ui/window.cc ui/dsn_configuration_window.cc ui/add_property_window.cc - system_dsn.cc) + win_system_dsn.cc) endif() -target_link_libraries(arrow_odbc_spi_impl PUBLIC odbcabstraction arrow_flight_sql_shared) +target_link_libraries(arrow_odbc_spi_impl PUBLIC odbcabstraction arrow_flight_sql_shared + arrow_compute_shared Boost::locale) -if(MSVC) - target_link_libraries(arrow_odbc_spi_impl PUBLIC Boost::locale) +# Link libraries on MINGW64 only +if(MINGW AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_link_libraries(arrow_odbc_spi_impl PUBLIC ${ODBCINST}) endif() set_target_properties(arrow_odbc_spi_impl @@ -132,9 +136,11 @@ add_arrow_test(arrow_odbc_spi_impl_test accessors/time_array_accessor_test.cc accessors/timestamp_array_accessor_test.cc flight_sql_connection_test.cc + flight_sql_stream_chunk_buffer_test.cc parse_table_types_test.cc json_converter_test.cc record_batch_transformer_test.cc utils_test.cc EXTRA_LINK_LIBS - arrow_odbc_spi_impl) + arrow_odbc_spi_impl + arrow_flight_testing_shared) diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/date_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/date_array_accessor_test.cc index d0c4d65099b2..769e87ce9e35 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/date_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/date_array_accessor_test.cc @@ -32,13 +32,16 @@ using arrow::NumericArray; using odbcabstraction::DATE_STRUCT; using odbcabstraction::OdbcVersion; -using odbcabstraction::tagDATE_STRUCT; using arrow::ArrayFromVector; using odbcabstraction::GetTimeForSecondsSinceEpoch; TEST(DateArrayAccessor, Test_Date32Array_CDataType_DATE) { - std::vector values = {7589, 12320, 18980, 19095}; + std::vector values = {7589, 12320, 18980, 19095, -1, 0}; + std::vector expected = { + {1990, 10, 12}, {2003, 9, 25}, {2021, 12, 19}, + {2022, 4, 13}, {1969, 12, 31}, {1970, 1, 1}, + }; std::shared_ptr array; ArrayFromVector(values, &array); @@ -46,7 +49,7 @@ TEST(DateArrayAccessor, Test_Date32Array_CDataType_DATE) { DateArrayFlightSqlAccessor accessor( dynamic_cast*>(array.get())); - std::vector buffer(values.size()); + std::vector buffer(values.size()); std::vector strlen_buffer(values.size()); ColumnBinding binding(odbcabstraction::CDataType_DATE, 0, 0, buffer.data(), 0, @@ -60,19 +63,31 @@ TEST(DateArrayAccessor, Test_Date32Array_CDataType_DATE) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(DATE_STRUCT), strlen_buffer[i]); - tm date{}; - int64_t converted_time = values[i] * 86400; - GetTimeForSecondsSinceEpoch(date, converted_time); - ASSERT_EQ((date.tm_year + 1900), buffer[i].year); - ASSERT_EQ(date.tm_mon + 1, buffer[i].month); - ASSERT_EQ(date.tm_mday, buffer[i].day); + ASSERT_EQ(expected[i].year, buffer[i].year); + ASSERT_EQ(expected[i].month, buffer[i].month); + ASSERT_EQ(expected[i].day, buffer[i].day); } } TEST(DateArrayAccessor, Test_Date64Array_CDataType_DATE) { - std::vector values = {86400000, 172800000, 259200000, 1649793238110, - 345600000, 432000000, 518400000}; + std::vector values = {86400000, 172800000, 259200000, 1649793238110, + 0, 345600000, 432000000, 518400000, + -86400000, -17987443200000}; + std::vector expected = { + /* year(16), month(u16), day(u16) */ + {1970, 1, 2}, + {1970, 1, 3}, + {1970, 1, 4}, + {2022, 4, 12}, + {1970, 1, 1}, + {1970, 1, 5}, + {1970, 1, 6}, + {1970, 1, 7}, + {1969, 12, 31}, + // This is the documented lower limit of supported Gregorian dates for boost + {1400, 1, 1}, + }; std::shared_ptr array; ArrayFromVector(values, &array); @@ -80,7 +95,7 @@ TEST(DateArrayAccessor, Test_Date64Array_CDataType_DATE) { DateArrayFlightSqlAccessor accessor( dynamic_cast*>(array.get())); - std::vector buffer(values.size()); + std::vector buffer(values.size()); std::vector strlen_buffer(values.size()); ColumnBinding binding(odbcabstraction::CDataType_DATE, 0, 0, buffer.data(), 0, @@ -96,11 +111,9 @@ TEST(DateArrayAccessor, Test_Date64Array_CDataType_DATE) { ASSERT_EQ(sizeof(DATE_STRUCT), strlen_buffer[i]); tm date{}; - int64_t converted_time = values[i] / 1000; - GetTimeForSecondsSinceEpoch(date, converted_time); - ASSERT_EQ((date.tm_year + 1900), buffer[i].year); - ASSERT_EQ(date.tm_mon + 1, buffer[i].month); - ASSERT_EQ(date.tm_mday, buffer[i].day); + ASSERT_EQ(expected[i].year, buffer[i].year); + ASSERT_EQ(expected[i].month, buffer[i].month); + ASSERT_EQ(expected[i].day, buffer[i].day); } } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/primitive_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/primitive_array_accessor_test.cc index 820c0a7bd840..abf18fa9ce8c 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/primitive_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/primitive_array_accessor_test.cc @@ -16,7 +16,7 @@ // under the License. #include "arrow/flight/sql/odbc/flight_sql/accessors/primitive_array_accessor.h" -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h" #include "arrow/testing/builder.h" #include "gtest/gtest.h" diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/string_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/string_array_accessor_test.cc index 8b568bbffcf5..587e7d5eb1cb 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/string_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/string_array_accessor_test.cc @@ -134,7 +134,6 @@ TEST(StringArrayAccessor, Test_CDataType_WCHAR_Truncation) { ColumnBinding binding(odbcabstraction::CDataType_WCHAR, 0, 0, buffer.data(), max_strlen, strlen_buffer.data()); - std::basic_stringstream ss; int64_t value_offset = 0; // Construct the whole string by concatenating smaller chunks from diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.cc index b85cb95a88af..e1e1b7404925 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.cc @@ -18,10 +18,13 @@ #include "arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/calendar_utils.h" +#include +#include + using arrow::TimeUnit; namespace { -int64_t GetConversionToSecondsDivisor(TimeUnit::type unit) { +inline int64_t GetConversionToSecondsDivisor(TimeUnit::type unit) { int64_t divisor = 1; switch (unit) { case TimeUnit::SECOND: @@ -44,25 +47,26 @@ int64_t GetConversionToSecondsDivisor(TimeUnit::type unit) { return divisor; } -uint32_t CalculateFraction(TimeUnit::type unit, uint64_t units_since_epoch) { +uint32_t CalculateFraction(TimeUnit::type unit, int64_t units_since_epoch) { // Convert the given remainder and time unit to nanoseconds // since the fraction field on TIMESTAMP_STRUCT is in nanoseconds. - switch (unit) { - case TimeUnit::SECOND: - return 0; - case TimeUnit::MILLI: - // 1000000 nanoseconds = 1 millisecond. - return (units_since_epoch % driver::odbcabstraction::MILLI_TO_SECONDS_DIVISOR) * - 1000000; - case TimeUnit::MICRO: - // 1000 nanoseconds = 1 microsecond. - return (units_since_epoch % driver::odbcabstraction::MICRO_TO_SECONDS_DIVISOR) * - 1000; - case TimeUnit::NANO: - // 1000 nanoseconds = 1 microsecond. - return (units_since_epoch % driver::odbcabstraction::NANO_TO_SECONDS_DIVISOR); - } - return 0; + if (unit == TimeUnit::SECOND) return 0; + + const int64_t divisor = GetConversionToSecondsDivisor(unit); + const int64_t nano_divisor = GetConversionToSecondsDivisor(TimeUnit::NANO); + + if (units_since_epoch < 0) + if (units_since_epoch <= + (std::numeric_limits::min() + divisor)) + // Prevent trying to derive and add a value larger than INT64_MAX (i.e. the time + // value at the start of the second which is used to shift the value positive before + // the modulo operation)) in next statement. + units_since_epoch += divisor; + // See below regarding floor division; here we want ceiling division. + // FIXME this goes poorly (trying to use a value > INT64_MAX when units_since_epoch is + // less than the smallest multiple of divisor greater than INT64_MIN. + units_since_epoch += divisor * std::abs((units_since_epoch - (divisor - 1)) / divisor); + return static_cast((units_since_epoch % divisor) * (nano_divisor / divisor)); } } // namespace @@ -84,11 +88,25 @@ RowStatus TimestampArrayFlightSqlAccessor::MoveSingleCell_imp ColumnBinding* binding, int64_t arrow_row, int64_t cell_counter, int64_t& value_offset, bool update_value_offset, odbcabstraction::Diagnostics& diagnostics) { + // Times less than the minimum integer number of seconds that can be represented + // for each time unit will not convert correctly. This is mostly interesting for + // nanoseconds as timestamps in other units are outside of the accepted range of + // Gregorian dates. auto* buffer = static_cast(binding->buffer); int64_t value = this->GetArray()->Value(arrow_row); const auto divisor = GetConversionToSecondsDivisor(UNIT); - const auto converted_result_seconds = value / divisor; + const auto converted_result_seconds = + // We want floor division here; C++ will round towards zero + (value < 0) + // Floor division: Shift all "fractional" (not a multiple of divisor) values so + // they round towards zero (and to the same value) along with the "floor" less + // than them, then add 1 to get back to the floor. Althernative we could shift + // negatively by (divisor - 1) but this breaks near INT64_MIN causing + // underflow.. + ? ((value + 1) / divisor) - 1 + // Towards zero is already floor + : value / divisor; tm timestamp = {0}; GetTimeForSecondsSinceEpoch(timestamp, converted_result_seconds); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor_test.cc index a5fb167e79d1..10860239e877 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/accessors/timestamp_array_accessor_test.cc @@ -34,8 +34,28 @@ using odbcabstraction::TIMESTAMP_STRUCT; using odbcabstraction::GetTimeForSecondsSinceEpoch; TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MILLI) { - std::vector values = {86400370, 172800000, 259200000, 1649793238110LL, - 345600000, 432000000, 518400000}; + std::vector values = { + 86400370, 172800000, 259200000, 1649793238110LL, 345600000, 432000000, 518400000, + -86399000, 0, -86399999, -86399001, 86400001, 86400999}; + std::vector expected = { + /* year(16), month(u16), day(u16), hour(u16), minute(u16), second(u16), + fraction(u32) */ + {1970, 1, 2, 0, 0, 0, 370000000}, + {1970, 1, 3, 0, 0, 0, 0}, + {1970, 1, 4, 0, 0, 0, 0}, + {2022, 4, 12, 19, 53, 58, 110000000}, + {1970, 1, 5, 0, 0, 0, 0}, + {1970, 1, 6, 0, 0, 0, 0}, + {1970, 1, 7, 0, 0, 0, 0}, + {1969, 12, 31, 0, 0, 1, 0}, + {1970, 1, 1, 0, 0, 0, 0}, + /* Tests both ends of the fraction rounding range to ensure we don't tip the wrong + way */ + {1969, 12, 31, 0, 0, 0, 1000000}, + {1969, 12, 31, 0, 0, 0, 999000000}, + {1970, 1, 2, 0, 0, 0, 1000000}, + {1970, 1, 2, 0, 0, 0, 999000000}, + }; std::shared_ptr timestamp_array; @@ -60,28 +80,26 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MILLI) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(TIMESTAMP_STRUCT), strlen_buffer[i]); - tm date{}; - - auto converted_time = values[i] / odbcabstraction::MILLI_TO_SECONDS_DIVISOR; - GetTimeForSecondsSinceEpoch(date, converted_time); - - ASSERT_EQ(buffer[i].year, 1900 + (date.tm_year)); - ASSERT_EQ(buffer[i].month, date.tm_mon + 1); - ASSERT_EQ(buffer[i].day, date.tm_mday); - ASSERT_EQ(buffer[i].hour, date.tm_hour); - ASSERT_EQ(buffer[i].minute, date.tm_min); - ASSERT_EQ(buffer[i].second, date.tm_sec); - - constexpr uint32_t NANOSECONDS_PER_MILLI = 1000000; - ASSERT_EQ( - buffer[i].fraction, - (values[i] % odbcabstraction::MILLI_TO_SECONDS_DIVISOR) * NANOSECONDS_PER_MILLI); + ASSERT_EQ(buffer[i].year, expected[i].year); + ASSERT_EQ(buffer[i].month, expected[i].month); + ASSERT_EQ(buffer[i].day, expected[i].day); + ASSERT_EQ(buffer[i].hour, expected[i].hour); + ASSERT_EQ(buffer[i].minute, expected[i].minute); + ASSERT_EQ(buffer[i].second, expected[i].second); + ASSERT_EQ(buffer[i].fraction, expected[i].fraction); } } TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_SECONDS) { - std::vector values = {86400, 172800, 259200, 1649793238, - 345600, 432000, 518400}; + std::vector values = {86400, 172800, 259200, 1649793238, 345600, + 432000, 518400, -86399, 0}; + std::vector expected = { + /* year(16), month(u16), day(u16), hour(u16), minute(u16), second(u16), + fraction(u32) */ + {1970, 1, 2, 0, 0, 0, 0}, {1970, 1, 3, 0, 0, 0, 0}, {1970, 1, 4, 0, 0, 0, 0}, + {2022, 4, 12, 19, 53, 58, 0}, {1970, 1, 5, 0, 0, 0, 0}, {1970, 1, 6, 0, 0, 0, 0}, + {1970, 1, 7, 0, 0, 0, 0}, {1969, 12, 31, 0, 0, 1, 0}, {1970, 1, 1, 0, 0, 0, 0}, + }; std::shared_ptr timestamp_array; @@ -106,23 +124,26 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_SECONDS) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(TIMESTAMP_STRUCT), strlen_buffer[i]); - tm date{}; - - auto converted_time = values[i]; - GetTimeForSecondsSinceEpoch(date, converted_time); - - ASSERT_EQ(buffer[i].year, 1900 + (date.tm_year)); - ASSERT_EQ(buffer[i].month, date.tm_mon + 1); - ASSERT_EQ(buffer[i].day, date.tm_mday); - ASSERT_EQ(buffer[i].hour, date.tm_hour); - ASSERT_EQ(buffer[i].minute, date.tm_min); - ASSERT_EQ(buffer[i].second, date.tm_sec); + ASSERT_EQ(buffer[i].year, expected[i].year); + ASSERT_EQ(buffer[i].month, expected[i].month); + ASSERT_EQ(buffer[i].day, expected[i].day); + ASSERT_EQ(buffer[i].hour, expected[i].hour); + ASSERT_EQ(buffer[i].minute, expected[i].minute); + ASSERT_EQ(buffer[i].second, expected[i].second); ASSERT_EQ(buffer[i].fraction, 0); } } TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MICRO) { - std::vector values = {86400000000, 1649793238000000}; + std::vector values = {0, 86400000000, 1649793238000000, -86399999999, + -86399000001}; + std::vector expected = { + /* year(16), month(u16), day(u16), hour(u16), minute(u16), second(u16), + fraction(u32) */ + {1970, 1, 1, 0, 0, 0, 0}, {1970, 1, 2, 0, 0, 0, 0}, + {2022, 4, 12, 19, 53, 58, 0}, {1969, 12, 31, 0, 0, 0, 1000}, + {1969, 12, 31, 0, 0, 0, 999999000}, + }; std::shared_ptr timestamp_array; @@ -148,25 +169,39 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MICRO) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(TIMESTAMP_STRUCT), strlen_buffer[i]); - tm date{}; - - auto converted_time = values[i] / odbcabstraction::MICRO_TO_SECONDS_DIVISOR; - GetTimeForSecondsSinceEpoch(date, converted_time); - - ASSERT_EQ(buffer[i].year, 1900 + (date.tm_year)); - ASSERT_EQ(buffer[i].month, date.tm_mon + 1); - ASSERT_EQ(buffer[i].day, date.tm_mday); - ASSERT_EQ(buffer[i].hour, date.tm_hour); - ASSERT_EQ(buffer[i].minute, date.tm_min); - ASSERT_EQ(buffer[i].second, date.tm_sec); - constexpr uint32_t MICROS_PER_NANO = 1000; - ASSERT_EQ(buffer[i].fraction, - (values[i] % odbcabstraction::MICRO_TO_SECONDS_DIVISOR) * MICROS_PER_NANO); + ASSERT_EQ(buffer[i].year, expected[i].year); + ASSERT_EQ(buffer[i].month, expected[i].month); + ASSERT_EQ(buffer[i].day, expected[i].day); + ASSERT_EQ(buffer[i].hour, expected[i].hour); + ASSERT_EQ(buffer[i].minute, expected[i].minute); + ASSERT_EQ(buffer[i].second, expected[i].second); + ASSERT_EQ(buffer[i].fraction, expected[i].fraction); } } TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_NANO) { - std::vector values = {86400000010000, 1649793238000000000}; + std::vector values = {86400000010000, + 1649793238000000000, + -86399999999999, + -86399000000001, + 86400000000001, + 86400999999999, + 0, + -9223372036000000001}; + std::vector expected = { + /* year(16), month(u16), day(u16), hour(u16), minute(u16), second(u16), + fraction(u32) */ + {1970, 1, 2, 0, 0, 0, 10000}, + {2022, 4, 12, 19, 53, 58, 0}, + {1969, 12, 31, 0, 0, 0, 1}, + {1969, 12, 31, 0, 0, 0, 999999999}, + {1970, 1, 2, 0, 0, 0, 1}, + {1970, 1, 2, 0, 0, 0, 999999999}, + {1970, 1, 1, 0, 0, 0, 0}, + /* Test within range where floor (seconds) value is below INT64_MIN in nanoseconds + */ + {1677, 9, 21, 0, 12, 43, 999999999}, + }; std::shared_ptr timestamp_array; @@ -191,18 +226,14 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_NANO) { for (size_t i = 0; i < values.size(); ++i) { ASSERT_EQ(sizeof(TIMESTAMP_STRUCT), strlen_buffer[i]); - tm date{}; - - auto converted_time = values[i] / odbcabstraction::NANO_TO_SECONDS_DIVISOR; - GetTimeForSecondsSinceEpoch(date, converted_time); - - ASSERT_EQ(buffer[i].year, 1900 + (date.tm_year)); - ASSERT_EQ(buffer[i].month, date.tm_mon + 1); - ASSERT_EQ(buffer[i].day, date.tm_mday); - ASSERT_EQ(buffer[i].hour, date.tm_hour); - ASSERT_EQ(buffer[i].minute, date.tm_min); - ASSERT_EQ(buffer[i].second, date.tm_sec); - ASSERT_EQ(buffer[i].fraction, (values[i] % odbcabstraction::NANO_TO_SECONDS_DIVISOR)); + + ASSERT_EQ(buffer[i].year, expected[i].year); + ASSERT_EQ(buffer[i].month, expected[i].month); + ASSERT_EQ(buffer[i].day, expected[i].day); + ASSERT_EQ(buffer[i].hour, expected[i].hour); + ASSERT_EQ(buffer[i].minute, expected[i].minute); + ASSERT_EQ(buffer[i].second, expected[i].second); + ASSERT_EQ(buffer[i].fraction, expected[i].fraction); } } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/address_info.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/address_info.h index 312d5689a989..91f5a7175d7c 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/address_info.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/address_info.h @@ -19,8 +19,10 @@ #include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" + #include +#include #if !_WIN32 # include #endif diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/config/configuration.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/config/configuration.cc index be92be057da0..db18239c47a1 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/config/configuration.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/config/configuration.cc @@ -17,6 +17,8 @@ #include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h" #include "arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h" +#include "arrow/result.h" +#include "arrow/util/utf8.h" #include #include @@ -27,7 +29,6 @@ namespace driver { namespace flight_sql { namespace config { - static const char DEFAULT_DSN[] = "Apache Arrow Flight SQL"; static const char DEFAULT_ENABLE_ENCRYPTION[] = TRUE_STR; static const char DEFAULT_USE_CERT_STORE[] = TRUE_STR; @@ -36,23 +37,27 @@ static const char DEFAULT_DISABLE_CERT_VERIFICATION[] = FALSE_STR; namespace { std::string ReadDsnString(const std::string& dsn, const std::string_view& key, const std::string& dflt = "") { -#define BUFFER_SIZE (1024) - std::vector buf(BUFFER_SIZE); + std::wstring wDsn = arrow::util::UTF8ToWideString(dsn).ValueOr(L""); + std::wstring wKey = arrow::util::UTF8ToWideString(key).ValueOr(L""); + std::wstring wDflt = arrow::util::UTF8ToWideString(dflt).ValueOr(L""); - std::string key_str = std::string(key); +#define BUFFER_SIZE (1024) + std::vector buf(BUFFER_SIZE); int ret = - SQLGetPrivateProfileString(dsn.c_str(), key_str.c_str(), dflt.c_str(), buf.data(), - static_cast(buf.size()), "ODBC.INI"); + SQLGetPrivateProfileString(wDsn.c_str(), wKey.c_str(), wDflt.c_str(), buf.data(), + static_cast(buf.size()), L"ODBC.INI"); if (ret > BUFFER_SIZE) { // If there wasn't enough space, try again with the right size buffer. buf.resize(ret + 1); ret = - SQLGetPrivateProfileString(dsn.c_str(), key_str.c_str(), dflt.c_str(), buf.data(), - static_cast(buf.size()), "ODBC.INI"); + SQLGetPrivateProfileString(wDsn.c_str(), wKey.c_str(), wDflt.c_str(), buf.data(), + static_cast(buf.size()), L"ODBC.INI"); } - return std::string(buf.data(), ret); + std::wstring wResult = std::wstring(buf.data(), ret); + std::string result = arrow::util::WideStringToUTF8(wResult).ValueOr(""); + return result; } void RemoveAllKnownKeys(std::vector& keys) { @@ -69,28 +74,32 @@ void RemoveAllKnownKeys(std::vector& keys) { } std::vector ReadAllKeys(const std::string& dsn) { - std::vector buf(BUFFER_SIZE); + std::wstring wDsn = arrow::util::UTF8ToWideString(dsn).ValueOr(L""); + + std::vector buf(BUFFER_SIZE); - int ret = SQLGetPrivateProfileString(dsn.c_str(), NULL, "", buf.data(), - static_cast(buf.size()), "ODBC.INI"); + int ret = SQLGetPrivateProfileString(wDsn.c_str(), NULL, L"", buf.data(), + static_cast(buf.size()), L"ODBC.INI"); if (ret > BUFFER_SIZE) { // If there wasn't enough space, try again with the right size buffer. buf.resize(ret + 1); - ret = SQLGetPrivateProfileString(dsn.c_str(), NULL, "", buf.data(), - static_cast(buf.size()), "ODBC.INI"); + ret = SQLGetPrivateProfileString(wDsn.c_str(), NULL, L"", buf.data(), + static_cast(buf.size()), L"ODBC.INI"); } // When you pass NULL to SQLGetPrivateProfileString it gives back a \0 delimited list of // all the keys. The below loop simply tokenizes all the keys and places them into a // vector. std::vector keys; - char* begin = buf.data(); + wchar_t* begin = buf.data(); while (begin && *begin != '\0') { - char* cur; + wchar_t* cur; for (cur = begin; *cur != '\0'; ++cur) { } - keys.emplace_back(begin, cur); + + std::string key = arrow::util::WideStringToUTF8(std::wstring(begin, cur)).ValueOr(""); + keys.emplace_back(key); begin = ++cur; } return keys; @@ -142,11 +151,11 @@ void Configuration::LoadDsn(const std::string& dsn) { void Configuration::Clear() { this->properties.clear(); } bool Configuration::IsSet(const std::string_view& key) const { - return 0 != this->properties.count(key); + return 0 != this->properties.count(std::string(key)); } const std::string& Configuration::Get(const std::string_view& key) const { - const auto itr = this->properties.find(key); + const auto itr = this->properties.find(std::string(key)); if (itr == this->properties.cend()) { static const std::string empty(""); return empty; @@ -154,10 +163,23 @@ const std::string& Configuration::Get(const std::string_view& key) const { return itr->second; } +void Configuration::Set(const std::string_view& key, const std::wstring& wValue) { + std::string value = arrow::util::WideStringToUTF8(wValue).ValueOr(""); + Set(key, value); +} + void Configuration::Set(const std::string_view& key, const std::string& value) { const std::string copy = boost::trim_copy(value); if (!copy.empty()) { - this->properties[key] = value; + this->properties[std::string(key)] = value; + } +} + +void Configuration::Emplace(const std::string_view& key, std::string&& value) { + const std::string copy = boost::trim_copy(value); + if (!copy.empty()) { + this->properties.emplace( + std::make_pair(std::move(std::string(key)), std::move(value))); } } @@ -166,16 +188,15 @@ const driver::odbcabstraction::Connection::ConnPropertyMap& Configuration::GetPr return this->properties; } -std::vector Configuration::GetCustomKeys() const { +std::vector Configuration::GetCustomKeys() const { driver::odbcabstraction::Connection::ConnPropertyMap copyProps(properties); for (auto& key : FlightSqlConnection::ALL_KEYS) { - copyProps.erase(key); + copyProps.erase(std::string(key)); } - std::vector keys; + std::vector keys; boost::copy(copyProps | boost::adaptors::map_keys, std::back_inserter(keys)); return keys; } - } // namespace config } // namespace flight_sql } // namespace driver diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_auth_method.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_auth_method.cc index fcf951270e62..b2d57e5df857 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_auth_method.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_auth_method.cc @@ -45,6 +45,10 @@ class NoOpAuthMethod : public FlightSqlAuthMethod { void Authenticate(FlightSqlConnection& connection, FlightCallOptions& call_options) override { // Do nothing + + // TODO: implement NoOpAuthMethod to validate server address. + // Can use NoOpClientAuthHandler. + // https://github.com/apache/arrow/issues/46733 } }; @@ -54,9 +58,9 @@ class NoOpClientAuthHandler : public arrow::flight::ClientAuthHandler { arrow::Status Authenticate(arrow::flight::ClientAuthSender* outgoing, arrow::flight::ClientAuthReader* incoming) override { - // Write a blank string. The server should ignore this and just accept any Handshake + // Return OK Status. The server should ignore this and just accept any Handshake // request. - return outgoing->Write(std::string()); + return arrow::Status::OK(); } arrow::Status GetToken(std::string* token) override { @@ -103,7 +107,9 @@ class UserPasswordAuthMethod : public FlightSqlAuthMethod { throw odbcabstraction::DriverException(bearer_result.status().message()); } - call_options.headers.push_back(bearer_result.ValueOrDie()); + // call_options may have already been populated with data from the connection string + // or DSN. Ensure auth-generated headers are placed at the front of the header list. + call_options.headers.insert(call_options.headers.begin(), bearer_result.ValueOrDie()); } std::string GetUser() override { return user_; } @@ -125,10 +131,11 @@ class TokenAuthMethod : public FlightSqlAuthMethod { void Authenticate(FlightSqlConnection& connection, FlightCallOptions& call_options) override { - // add the token to the headers + // add the token to the front of the headers. For consistency auth headers should be + // at the front. const std::pair token_header("authorization", "Bearer " + token_); - call_options.headers.push_back(token_header); + call_options.headers.insert(call_options.headers.begin(), token_header); const arrow::Status status = client_.Authenticate( call_options, @@ -153,22 +160,22 @@ std::unique_ptr FlightSqlAuthMethod::FromProperties( const std::unique_ptr& client, const Connection::ConnPropertyMap& properties) { // Check if should use user-password authentication - auto it_user = properties.find(FlightSqlConnection::USER); + auto it_user = properties.find(std::string(FlightSqlConnection::USER)); if (it_user == properties.end()) { // The Microsoft OLE DB to ODBC bridge provider (MSDASQL) will write // "User ID" and "Password" properties instead of mapping // to ODBC compliant UID/PWD keys. - it_user = properties.find(FlightSqlConnection::USER_ID); + it_user = properties.find(std::string(FlightSqlConnection::USER_ID)); } - auto it_password = properties.find(FlightSqlConnection::PASSWORD); - auto it_token = properties.find(FlightSqlConnection::TOKEN); + auto it_password = properties.find(std::string(FlightSqlConnection::PASSWORD)); + auto it_token = properties.find(std::string(FlightSqlConnection::TOKEN)); if (it_user == properties.end() || it_password == properties.end()) { // Accept UID/PWD as aliases for User/Password. These are suggested as // standard properties in the documentation for SQLDriverConnect. - it_user = properties.find(FlightSqlConnection::UID); - it_password = properties.find(FlightSqlConnection::PWD); + it_user = properties.find(std::string(FlightSqlConnection::UID)); + it_password = properties.find(std::string(FlightSqlConnection::PWD)); } if (it_user != properties.end() || it_password != properties.end()) { const std::string& user = it_user != properties.end() ? it_user->second : ""; diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection.cc index 09764e5c18ba..c87c394fc314 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection.cc @@ -83,7 +83,7 @@ namespace { #if _WIN32 || _WIN64 constexpr auto SYSTEM_TRUST_STORE_DEFAULT = true; -constexpr auto STORES = {"CA", "MY", "ROOT", "SPC"}; +constexpr auto STORES = {L"CA", L"MY", L"ROOT", L"SPC"}; inline std::string GetCerts() { std::string certs; @@ -111,26 +111,28 @@ inline std::string GetCerts() { return ""; } #endif -const std::set - BUILT_IN_PROPERTIES = {FlightSqlConnection::HOST, - FlightSqlConnection::PORT, - FlightSqlConnection::USER, - FlightSqlConnection::USER_ID, - FlightSqlConnection::UID, - FlightSqlConnection::PASSWORD, - FlightSqlConnection::PWD, - FlightSqlConnection::TOKEN, - FlightSqlConnection::USE_ENCRYPTION, - FlightSqlConnection::DISABLE_CERTIFICATE_VERIFICATION, - FlightSqlConnection::TRUSTED_CERTS, - FlightSqlConnection::USE_SYSTEM_TRUST_STORE, - FlightSqlConnection::STRING_COLUMN_LENGTH, - FlightSqlConnection::USE_WIDE_CHAR}; +const std::set BUILT_IN_PROPERTIES = { + FlightSqlConnection::DRIVER, + FlightSqlConnection::DSN, + FlightSqlConnection::HOST, + FlightSqlConnection::PORT, + FlightSqlConnection::USER, + FlightSqlConnection::USER_ID, + FlightSqlConnection::UID, + FlightSqlConnection::PASSWORD, + FlightSqlConnection::PWD, + FlightSqlConnection::TOKEN, + FlightSqlConnection::USE_ENCRYPTION, + FlightSqlConnection::DISABLE_CERTIFICATE_VERIFICATION, + FlightSqlConnection::TRUSTED_CERTS, + FlightSqlConnection::USE_SYSTEM_TRUST_STORE, + FlightSqlConnection::STRING_COLUMN_LENGTH, + FlightSqlConnection::USE_WIDE_CHAR}; Connection::ConnPropertyMap::const_iterator TrackMissingRequiredProperty( const std::string_view& property, const Connection::ConnPropertyMap& properties, std::vector& missing_attr) { - auto prop_iter = properties.find(property); + auto prop_iter = properties.find(std::string(property)); if (properties.end() == prop_iter) { missing_attr.push_back(property); } @@ -149,7 +151,8 @@ std::shared_ptr LoadFlightSslConfigs( AsBool(connPropertyMap, FlightSqlConnection::USE_SYSTEM_TRUST_STORE) .value_or(SYSTEM_TRUST_STORE_DEFAULT); - auto trusted_certs_iterator = connPropertyMap.find(FlightSqlConnection::TRUSTED_CERTS); + auto trusted_certs_iterator = + connPropertyMap.find(std::string(FlightSqlConnection::TRUSTED_CERTS)); auto trusted_certs = trusted_certs_iterator != connPropertyMap.end() ? trusted_certs_iterator->second : ""; @@ -164,15 +167,18 @@ void FlightSqlConnection::Connect(const ConnPropertyMap& properties, auto flight_ssl_configs = LoadFlightSslConfigs(properties); Location location = BuildLocation(properties, missing_attr, flight_ssl_configs); - FlightClientOptions client_options = + client_options_ = BuildFlightClientOptions(properties, missing_attr, flight_ssl_configs); const std::shared_ptr& cookie_factory = arrow::flight::GetCookieFactory(); - client_options.middleware.push_back(cookie_factory); + client_options_.middleware.push_back(cookie_factory); std::unique_ptr flight_client; - ThrowIfNotOK(FlightClient::Connect(location, client_options).Value(&flight_client)); + ThrowIfNotOK(FlightClient::Connect(location, client_options_).Value(&flight_client)); + + PopulateMetadataSettings(properties); + PopulateCallOptions(properties); std::unique_ptr auth_method = FlightSqlAuthMethod::FromProperties(flight_client, properties); @@ -187,9 +193,6 @@ void FlightSqlConnection::Connect(const ConnPropertyMap& properties, info_.SetProperty(SQL_USER_NAME, auth_method->GetUser()); attribute_[CONNECTION_DEAD] = static_cast(SQL_FALSE); - - PopulateMetadataSettings(properties); - PopulateCallOptions(properties); } catch (...) { attribute_[CONNECTION_DEAD] = static_cast(SQL_TRUE); sql_client_.reset(); @@ -376,7 +379,7 @@ void FlightSqlConnection::Close() { std::shared_ptr FlightSqlConnection::CreateStatement() { return std::shared_ptr(new FlightSqlStatement( - diagnostics_, *sql_client_, call_options_, metadata_settings_)); + diagnostics_, *sql_client_, client_options_, call_options_, metadata_settings_)); } bool FlightSqlConnection::SetAttribute(Connection::AttributeId attribute, @@ -422,7 +425,7 @@ FlightSqlConnection::FlightSqlConnection(OdbcVersion odbc_version, const std::string& driver_version) : diagnostics_("Apache Arrow", "Flight SQL", odbc_version), odbc_version_(odbc_version), - info_(call_options_, sql_client_, driver_version), + info_(client_options_, call_options_, sql_client_, driver_version), closed_(true) { attribute_[CONNECTION_DEAD] = static_cast(SQL_TRUE); attribute_[LOGIN_TIMEOUT] = static_cast(0); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h index 0ee6d5d53913..0a4b213229ff 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h @@ -29,6 +29,13 @@ namespace driver { namespace flight_sql { +/// \brief Case insensitive comparator that takes string_view +struct CaseInsensitiveComparatorStrView { + bool operator()(const std::string_view& s1, const std::string_view& s2) const { + return boost::lexicographical_compare(s1, s2, boost::is_iless()); + } +}; + class FlightSqlSslConfig; /// \brief Create an instance of the FlightSqlSslConfig class, from the properties passed diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection_test.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection_test.cc index 6a519138b637..a7a0fc10c295 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection_test.cc @@ -69,10 +69,12 @@ TEST(MetadataSettingsTest, StringColumnLengthTest) { const int32_t expected_string_column_length = 100000; const Connection::ConnPropertyMap properties = { - {FlightSqlConnection::HOST, std::string("localhost")}, // expect not used - {FlightSqlConnection::PORT, std::string("32010")}, // expect not used - {FlightSqlConnection::USE_ENCRYPTION, std::string("false")}, // expect not used - {FlightSqlConnection::STRING_COLUMN_LENGTH, + {std::string(FlightSqlConnection::HOST), + std::string("localhost")}, // expect not used + {std::string(FlightSqlConnection::PORT), std::string("32010")}, // expect not used + {std::string(FlightSqlConnection::USE_ENCRYPTION), + std::string("false")}, // expect not used + {std::string(FlightSqlConnection::STRING_COLUMN_LENGTH), std::to_string(expected_string_column_length)}, }; @@ -90,10 +92,10 @@ TEST(MetadataSettingsTest, UseWideCharTest) { connection.SetClosed(false); const Connection::ConnPropertyMap properties1 = { - {FlightSqlConnection::USE_WIDE_CHAR, std::string("true")}, + {std::string(FlightSqlConnection::USE_WIDE_CHAR), std::string("true")}, }; const Connection::ConnPropertyMap properties2 = { - {FlightSqlConnection::USE_WIDE_CHAR, std::string("false")}, + {std::string(FlightSqlConnection::USE_WIDE_CHAR), std::string("false")}, }; EXPECT_EQ(true, connection.GetUseWideChar(properties1)); @@ -105,9 +107,9 @@ TEST(MetadataSettingsTest, UseWideCharTest) { TEST(BuildLocationTests, ForTcp) { std::vector missing_attr; Connection::ConnPropertyMap properties = { - {FlightSqlConnection::HOST, std::string("localhost")}, - {FlightSqlConnection::PORT, std::string("32010")}, - {FlightSqlConnection::USE_ENCRYPTION, std::string("false")}, + {std::string(FlightSqlConnection::HOST), std::string("localhost")}, + {std::string(FlightSqlConnection::PORT), std::string("32010")}, + {std::string(FlightSqlConnection::USE_ENCRYPTION), std::string("false")}, }; const std::shared_ptr& ssl_config = @@ -117,8 +119,8 @@ TEST(BuildLocationTests, ForTcp) { FlightSqlConnection::BuildLocation(properties, missing_attr, ssl_config); const Location& actual_location2 = FlightSqlConnection::BuildLocation( { - {FlightSqlConnection::HOST, std::string("localhost")}, - {FlightSqlConnection::PORT, std::string("32011")}, + {std::string(FlightSqlConnection::HOST), std::string("localhost")}, + {std::string(FlightSqlConnection::PORT), std::string("32011")}, }, missing_attr, ssl_config); @@ -131,9 +133,9 @@ TEST(BuildLocationTests, ForTcp) { TEST(BuildLocationTests, ForTls) { std::vector missing_attr; Connection::ConnPropertyMap properties = { - {FlightSqlConnection::HOST, std::string("localhost")}, - {FlightSqlConnection::PORT, std::string("32010")}, - {FlightSqlConnection::USE_ENCRYPTION, std::string("1")}, + {std::string(FlightSqlConnection::HOST), std::string("localhost")}, + {std::string(FlightSqlConnection::PORT), std::string("32010")}, + {std::string(FlightSqlConnection::USE_ENCRYPTION), std::string("1")}, }; const std::shared_ptr& ssl_config = @@ -143,9 +145,9 @@ TEST(BuildLocationTests, ForTls) { FlightSqlConnection::BuildLocation(properties, missing_attr, ssl_config); Connection::ConnPropertyMap second_properties = { - {FlightSqlConnection::HOST, std::string("localhost")}, - {FlightSqlConnection::PORT, std::string("32011")}, - {FlightSqlConnection::USE_ENCRYPTION, std::string("1")}, + {std::string(FlightSqlConnection::HOST), std::string("localhost")}, + {std::string(FlightSqlConnection::PORT), std::string("32011")}, + {std::string(FlightSqlConnection::USE_ENCRYPTION), std::string("1")}, }; const std::shared_ptr& second_ssl_config = diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_driver.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_driver.cc index 1949d2f15adc..0736dac8486b 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_driver.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_driver.cc @@ -16,13 +16,17 @@ // under the License. #include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/flight_sql_driver.h" +#include "arrow/compute/api.h" #include "arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h" +#include "arrow/flight/sql/odbc/flight_sql/utils.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spd_logger.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/utils.h" +#include "arrow/util/io_util.h" #define DEFAULT_MAXIMUM_FILE_SIZE 16777216 #define CONFIG_FILE_NAME "arrow-odbc.ini" +#define CONFIG_FILE_PATH "CONFIG_FILE_PATH" namespace driver { namespace flight_sql { @@ -52,7 +56,11 @@ LogLevel ToLogLevel(int64_t level) { } // namespace FlightSqlDriver::FlightSqlDriver() - : diagnostics_("Apache Arrow", "Flight SQL", OdbcVersion::V_3), version_("0.9.0.0") {} + : diagnostics_("Apache Arrow", "Flight SQL", OdbcVersion::V_3), version_("0.9.0.0") { + RegisterLog(); + // Register Kernel functions to library + ThrowIfNotOK(arrow::compute::Initialize()); +} std::shared_ptr FlightSqlDriver::CreateConnection(OdbcVersion odbc_version) { return std::make_shared(odbc_version, version_); @@ -63,24 +71,29 @@ odbcabstraction::Diagnostics& FlightSqlDriver::GetDiagnostics() { return diagnos void FlightSqlDriver::SetVersion(std::string version) { version_ = std::move(version); } void FlightSqlDriver::RegisterLog() { + std::string config_path = arrow::internal::GetEnvVar(CONFIG_FILE_PATH).ValueOr(""); + if (config_path.empty()) { + return; + } + odbcabstraction::PropertyMap propertyMap; - driver::odbcabstraction::ReadConfigFile(propertyMap, CONFIG_FILE_NAME); + driver::odbcabstraction::ReadConfigFile(propertyMap, config_path, CONFIG_FILE_NAME); - auto log_enable_iterator = propertyMap.find(SPDLogger::LOG_ENABLED); + auto log_enable_iterator = propertyMap.find(std::string(SPDLogger::LOG_ENABLED)); auto log_enabled = log_enable_iterator != propertyMap.end() ? odbcabstraction::AsBool(log_enable_iterator->second) : false; - if (!log_enabled) { + if (!log_enabled.get()) { return; } - auto log_path_iterator = propertyMap.find(SPDLogger::LOG_PATH); + auto log_path_iterator = propertyMap.find(std::string(SPDLogger::LOG_PATH)); auto log_path = log_path_iterator != propertyMap.end() ? log_path_iterator->second : ""; if (log_path.empty()) { return; } - auto log_level_iterator = propertyMap.find(SPDLogger::LOG_LEVEL); + auto log_level_iterator = propertyMap.find(std::string(SPDLogger::LOG_LEVEL)); auto log_level = ToLogLevel(log_level_iterator != propertyMap.end() ? std::stoi(log_level_iterator->second) : 1); @@ -88,12 +101,14 @@ void FlightSqlDriver::RegisterLog() { return; } - auto maximum_file_size_iterator = propertyMap.find(SPDLogger::MAXIMUM_FILE_SIZE); + auto maximum_file_size_iterator = + propertyMap.find(std::string(SPDLogger::MAXIMUM_FILE_SIZE)); auto maximum_file_size = maximum_file_size_iterator != propertyMap.end() ? std::stoi(maximum_file_size_iterator->second) : DEFAULT_MAXIMUM_FILE_SIZE; - auto maximum_file_quantity_iterator = propertyMap.find(SPDLogger::FILE_QUANTITY); + auto maximum_file_quantity_iterator = + propertyMap.find(std::string(SPDLogger::FILE_QUANTITY)); auto maximum_file_quantity = maximum_file_quantity_iterator != propertyMap.end() ? std::stoi(maximum_file_quantity_iterator->second) : 1; diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_get_tables_reader.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_get_tables_reader.cc index ccd6058f8cd3..b048d1984c5c 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_get_tables_reader.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_get_tables_reader.cc @@ -80,9 +80,10 @@ std::shared_ptr GetTablesReader::GetSchema() { const arrow::Result>& result = arrow::ipc::ReadSchema(&dataset_schema_reader, &in_memo); if (!result.ok()) { - // TODO: Ignoring this error until we fix the problem on Dremio server - // The problem is that complex types columns are being returned without the children - // types. + // TODO: Test and build the driver against a server that returns + // complex types columns with the children + // types and handle the failure properly + // https://github.com/apache/arrow/issues/46561 return nullptr; } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.cc index 824260a68687..258c810996ac 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.cc @@ -18,6 +18,8 @@ #include "arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" +#include + #include #include "arrow/flight/types.h" #include "arrow/scalar.h" @@ -42,13 +44,14 @@ using odbcabstraction::DriverException; FlightSqlResultSet::FlightSqlResultSet( FlightSqlClient& flight_sql_client, + const arrow::flight::FlightClientOptions& client_options, const arrow::flight::FlightCallOptions& call_options, const std::shared_ptr& flight_info, const std::shared_ptr& transformer, odbcabstraction::Diagnostics& diagnostics, const odbcabstraction::MetadataSettings& metadata_settings) : metadata_settings_(metadata_settings), - chunk_buffer_(flight_sql_client, call_options, flight_info, + chunk_buffer_(flight_sql_client, client_options, call_options, flight_info, metadata_settings_.chunk_buffer_capacity_), transformer_(transformer), metadata_(transformer @@ -226,14 +229,14 @@ void FlightSqlResultSet::Cancel() { current_chunk_.data = nullptr; } -bool FlightSqlResultSet::GetData(int column_n, int16_t target_type, int precision, - int scale, void* buffer, size_t buffer_length, - ssize_t* strlen_buffer) { +SQLRETURN FlightSqlResultSet::GetData(int column_n, int16_t target_type, int precision, + int scale, void* buffer, size_t buffer_length, + ssize_t* strlen_buffer) { reset_get_data_ = true; // Check if the offset is already at the end. int64_t& value_offset = get_data_offsets_[column_n - 1]; if (value_offset == -1) { - return false; + return SQL_NO_DATA; } ColumnBinding binding(ConvertCDataTypeFromV2ToV3(target_type), precision, scale, buffer, @@ -249,7 +252,11 @@ bool FlightSqlResultSet::GetData(int column_n, int16_t target_type, int precisio diagnostics_, nullptr); // If there was truncation, the converter would have reported it to the diagnostics. - return diagnostics_.HasWarning(); + if (diagnostics_.HasWarning()) { + return SQL_SUCCESS_WITH_INFO; + } else { + return SQL_SUCCESS; + } } std::shared_ptr FlightSqlResultSet::GetMetadata() { return metadata_; } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h index d1f20979a240..5a03b16f0660 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h @@ -63,6 +63,7 @@ class FlightSqlResultSet : public ResultSet { ~FlightSqlResultSet() override; FlightSqlResultSet(FlightSqlClient& flight_sql_client, + const arrow::flight::FlightClientOptions& client_options, const arrow::flight::FlightCallOptions& call_options, const std::shared_ptr& flight_info, const std::shared_ptr& transformer, @@ -73,8 +74,8 @@ class FlightSqlResultSet : public ResultSet { void Cancel() override; - bool GetData(int column_n, int16_t target_type, int precision, int scale, void* buffer, - size_t buffer_length, ssize_t* strlen_buffer) override; + SQLRETURN GetData(int column_n, int16_t target_type, int precision, int scale, + void* buffer, size_t buffer_length, ssize_t* strlen_buffer) override; size_t Move(size_t rows, size_t bind_offset, size_t bind_type, uint16_t* row_status_array) override; diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_accessors.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_accessors.h index 3f7d68560832..1d5014140ef9 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_accessors.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_accessors.h @@ -17,9 +17,9 @@ #pragma once -#include -#include #include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" +#include "arrow/type_fwd.h" namespace driver { namespace flight_sql { diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.cc index f863d4bc4894..710f7608ecfc 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.cc @@ -16,10 +16,10 @@ // under the License. #include "arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.h" -#include -#include +#include "arrow/flight/sql/column_metadata.h" #include "arrow/flight/sql/odbc/flight_sql/utils.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" +#include "arrow/util/key_value_metadata.h" #include #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" @@ -260,18 +260,29 @@ bool FlightSqlResultSetMetadata::IsUnsigned(int column_position) { const std::shared_ptr& field = schema_->field(column_position - 1); switch (field->type()->id()) { + case arrow::Type::INT8: + case arrow::Type::INT16: + case arrow::Type::INT32: + case arrow::Type::INT64: + case arrow::Type::DOUBLE: + case arrow::Type::FLOAT: + case arrow::Type::HALF_FLOAT: + case arrow::Type::DECIMAL32: + case arrow::Type::DECIMAL64: + case arrow::Type::DECIMAL128: + case arrow::Type::DECIMAL256: + return false; case arrow::Type::UINT8: case arrow::Type::UINT16: case arrow::Type::UINT32: case arrow::Type::UINT64: - return true; default: - return false; + return true; } } bool FlightSqlResultSetMetadata::IsFixedPrecScale(int column_position) { - // TODO: Flight SQL column metadata does not have this, should we add to the spec? + // Precision for Arrow data types are modifiable by the user return false; } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.h index f8e78eb2d6de..29901652c521 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_result_set_metadata.h @@ -89,6 +89,7 @@ class FlightSqlResultSetMetadata : public odbcabstraction::ResultSetMetadata { odbcabstraction::Searchability IsSearchable(int column_position) override; + /// \brief Returns true if the column is unsigned (not numeric) bool IsUnsigned(int column_position) override; bool IsFixedPrecScale(int column_position) override; diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_ssl_config.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_ssl_config.h index 76a54f13ce12..2369f0aab4d2 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_ssl_config.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_ssl_config.h @@ -17,9 +17,9 @@ #pragma once -#include -#include #include +#include "arrow/flight/types.h" +#include "arrow/status.h" namespace driver { namespace flight_sql { diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement.cc index 1e8498ad7e35..efe333d836a4 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement.cc @@ -52,9 +52,10 @@ using driver::odbcabstraction::Statement; namespace { void ClosePreparedStatementIfAny( - std::shared_ptr& prepared_statement) { + std::shared_ptr& prepared_statement, + const FlightCallOptions& options) { if (prepared_statement != nullptr) { - ThrowIfNotOK(prepared_statement->Close()); + ThrowIfNotOK(prepared_statement->Close(options)); prepared_statement.reset(); } } @@ -63,11 +64,12 @@ void ClosePreparedStatementIfAny( FlightSqlStatement::FlightSqlStatement( const odbcabstraction::Diagnostics& diagnostics, FlightSqlClient& sql_client, - FlightCallOptions call_options, + arrow::flight::FlightClientOptions client_options, FlightCallOptions call_options, const odbcabstraction::MetadataSettings& metadata_settings) : diagnostics_("Apache Arrow", diagnostics.GetDataSourceComponent(), diagnostics.GetOdbcVersion()), sql_client_(sql_client), + client_options_(std::move(client_options)), call_options_(std::move(call_options)), metadata_settings_(metadata_settings) { attribute_[METADATA_ID] = static_cast(SQL_FALSE); @@ -77,6 +79,10 @@ FlightSqlStatement::FlightSqlStatement( call_options_.timeout = TimeoutDuration{-1}; } +FlightSqlStatement::~FlightSqlStatement() { + ClosePreparedStatementIfAny(prepared_statement_, call_options_); +} + bool FlightSqlStatement::SetAttribute(StatementAttributeId attribute, const Attribute& value) { switch (attribute) { @@ -108,7 +114,7 @@ boost::optional FlightSqlStatement::GetAttribute( boost::optional> FlightSqlStatement::Prepare( const std::string& query) { - ClosePreparedStatementIfAny(prepared_statement_); + ClosePreparedStatementIfAny(prepared_statement_, call_options_); Result> result = sql_client_.Prepare(call_options_, query); @@ -124,25 +130,27 @@ boost::optional> FlightSqlStatement::Prepare( bool FlightSqlStatement::ExecutePrepared() { assert(prepared_statement_.get() != nullptr); - Result> result = prepared_statement_->Execute(); + Result> result = + prepared_statement_->Execute(call_options_); + ThrowIfNotOK(result.status()); current_result_set_ = std::make_shared( - sql_client_, call_options_, result.ValueOrDie(), nullptr, diagnostics_, - metadata_settings_); + sql_client_, client_options_, call_options_, result.ValueOrDie(), nullptr, + diagnostics_, metadata_settings_); return true; } bool FlightSqlStatement::Execute(const std::string& query) { - ClosePreparedStatementIfAny(prepared_statement_); + ClosePreparedStatementIfAny(prepared_statement_, call_options_); Result> result = sql_client_.Execute(call_options_, query); ThrowIfNotOK(result.status()); current_result_set_ = std::make_shared( - sql_client_, call_options_, result.ValueOrDie(), nullptr, diagnostics_, - metadata_settings_); + sql_client_, client_options_, call_options_, result.ValueOrDie(), nullptr, + diagnostics_, metadata_settings_); return true; } @@ -157,33 +165,35 @@ std::shared_ptr FlightSqlStatement::GetTables( const std::string* catalog_name, const std::string* schema_name, const std::string* table_name, const std::string* table_type, const ColumnNames& column_names) { - ClosePreparedStatementIfAny(prepared_statement_); + ClosePreparedStatementIfAny(prepared_statement_, call_options_); std::vector table_types; if ((catalog_name && *catalog_name == "%") && (schema_name && schema_name->empty()) && (table_name && table_name->empty())) { - current_result_set_ = GetTablesForSQLAllCatalogs( - column_names, call_options_, sql_client_, diagnostics_, metadata_settings_); + current_result_set_ = + GetTablesForSQLAllCatalogs(column_names, client_options_, call_options_, + sql_client_, diagnostics_, metadata_settings_); } else if ((catalog_name && catalog_name->empty()) && (schema_name && *schema_name == "%") && (table_name && table_name->empty())) { - current_result_set_ = - GetTablesForSQLAllDbSchemas(column_names, call_options_, sql_client_, schema_name, - diagnostics_, metadata_settings_); + current_result_set_ = GetTablesForSQLAllDbSchemas( + column_names, client_options_, call_options_, sql_client_, schema_name, + diagnostics_, metadata_settings_); } else if ((catalog_name && catalog_name->empty()) && (schema_name && schema_name->empty()) && (table_name && table_name->empty()) && (table_type && *table_type == "%")) { - current_result_set_ = GetTablesForSQLAllTableTypes( - column_names, call_options_, sql_client_, diagnostics_, metadata_settings_); + current_result_set_ = + GetTablesForSQLAllTableTypes(column_names, client_options_, call_options_, + sql_client_, diagnostics_, metadata_settings_); } else { if (table_type) { ParseTableTypes(*table_type, table_types); } current_result_set_ = GetTablesForGenericUse( - column_names, call_options_, sql_client_, catalog_name, schema_name, table_name, - table_types, diagnostics_, metadata_settings_); + column_names, client_options_, call_options_, sql_client_, catalog_name, + schema_name, table_name, table_types, diagnostics_, metadata_settings_); } return current_result_set_; @@ -210,7 +220,7 @@ std::shared_ptr FlightSqlStatement::GetTables_V3( std::shared_ptr FlightSqlStatement::GetColumns_V2( const std::string* catalog_name, const std::string* schema_name, const std::string* table_name, const std::string* column_name) { - ClosePreparedStatementIfAny(prepared_statement_); + ClosePreparedStatementIfAny(prepared_statement_, call_options_); Result> result = sql_client_.GetTables( call_options_, catalog_name, schema_name, table_name, true, nullptr); @@ -221,9 +231,9 @@ std::shared_ptr FlightSqlStatement::GetColumns_V2( auto transformer = std::make_shared( metadata_settings_, odbcabstraction::V_2, column_name); - current_result_set_ = - std::make_shared(sql_client_, call_options_, flight_info, - transformer, diagnostics_, metadata_settings_); + current_result_set_ = std::make_shared( + sql_client_, client_options_, call_options_, flight_info, transformer, diagnostics_, + metadata_settings_); return current_result_set_; } @@ -231,7 +241,7 @@ std::shared_ptr FlightSqlStatement::GetColumns_V2( std::shared_ptr FlightSqlStatement::GetColumns_V3( const std::string* catalog_name, const std::string* schema_name, const std::string* table_name, const std::string* column_name) { - ClosePreparedStatementIfAny(prepared_statement_); + ClosePreparedStatementIfAny(prepared_statement_, call_options_); Result> result = sql_client_.GetTables( call_options_, catalog_name, schema_name, table_name, true, nullptr); @@ -242,15 +252,15 @@ std::shared_ptr FlightSqlStatement::GetColumns_V3( auto transformer = std::make_shared( metadata_settings_, odbcabstraction::V_3, column_name); - current_result_set_ = - std::make_shared(sql_client_, call_options_, flight_info, - transformer, diagnostics_, metadata_settings_); + current_result_set_ = std::make_shared( + sql_client_, client_options_, call_options_, flight_info, transformer, diagnostics_, + metadata_settings_); return current_result_set_; } std::shared_ptr FlightSqlStatement::GetTypeInfo_V2(int16_t data_type) { - ClosePreparedStatementIfAny(prepared_statement_); + ClosePreparedStatementIfAny(prepared_statement_, call_options_); Result> result = sql_client_.GetXdbcTypeInfo(call_options_); ThrowIfNotOK(result.status()); @@ -260,15 +270,15 @@ std::shared_ptr FlightSqlStatement::GetTypeInfo_V2(int16_t data_type) auto transformer = std::make_shared( metadata_settings_, odbcabstraction::V_2, data_type); - current_result_set_ = - std::make_shared(sql_client_, call_options_, flight_info, - transformer, diagnostics_, metadata_settings_); + current_result_set_ = std::make_shared( + sql_client_, client_options_, call_options_, flight_info, transformer, diagnostics_, + metadata_settings_); return current_result_set_; } std::shared_ptr FlightSqlStatement::GetTypeInfo_V3(int16_t data_type) { - ClosePreparedStatementIfAny(prepared_statement_); + ClosePreparedStatementIfAny(prepared_statement_, call_options_); Result> result = sql_client_.GetXdbcTypeInfo(call_options_); ThrowIfNotOK(result.status()); @@ -278,9 +288,9 @@ std::shared_ptr FlightSqlStatement::GetTypeInfo_V3(int16_t data_type) auto transformer = std::make_shared( metadata_settings_, odbcabstraction::V_3, data_type); - current_result_set_ = - std::make_shared(sql_client_, call_options_, flight_info, - transformer, diagnostics_, metadata_settings_); + current_result_set_ = std::make_shared( + sql_client_, client_options_, call_options_, flight_info, transformer, diagnostics_, + metadata_settings_); return current_result_set_; } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement.h index 7ffb02ba40bf..00fe9137f51b 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement.h @@ -33,6 +33,7 @@ class FlightSqlStatement : public odbcabstraction::Statement { private: odbcabstraction::Diagnostics diagnostics_; std::map attribute_; + arrow::flight::FlightClientOptions client_options_; arrow::flight::FlightCallOptions call_options_; arrow::flight::sql::FlightSqlClient& sql_client_; std::shared_ptr current_result_set_; @@ -48,8 +49,10 @@ class FlightSqlStatement : public odbcabstraction::Statement { public: FlightSqlStatement(const odbcabstraction::Diagnostics& diagnostics, arrow::flight::sql::FlightSqlClient& sql_client, + arrow::flight::FlightClientOptions client_options, arrow::flight::FlightCallOptions call_options, const odbcabstraction::MetadataSettings& metadata_settings); + ~FlightSqlStatement(); bool SetAttribute(StatementAttributeId attribute, const Attribute& value) override; 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 0e250d1af9be..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 @@ -98,10 +98,10 @@ Result> Transform_inner( const auto& table_name = reader.GetTableName(); const std::shared_ptr& schema = reader.GetSchema(); if (schema == nullptr) { - // TODO: Remove this if after fixing TODO on GetTablesReader::GetSchema() - // This is because of a problem on Dremio server, where complex types columns - // are being returned without the children types, so we are simply ignoring - // it by now. + // TODO: Test and build the driver against a server that returns + // complex types columns with the children + // types and handle the failure properly. + // https://github.com/apache/arrow/issues/46561 continue; } for (int i = 0; i < schema->num_fields(); ++i) { diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.cc index a3cdf9768d20..ba3389ccba97 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.cc @@ -72,8 +72,9 @@ void ParseTableTypes(const std::string& table_type, } std::shared_ptr GetTablesForSQLAllCatalogs( - const ColumnNames& names, FlightCallOptions& call_options, - FlightSqlClient& sql_client, odbcabstraction::Diagnostics& diagnostics, + const ColumnNames& names, FlightClientOptions& client_options, + FlightCallOptions& call_options, FlightSqlClient& sql_client, + odbcabstraction::Diagnostics& diagnostics, const odbcabstraction::MetadataSettings& metadata_settings) { Result> result = sql_client.GetCatalogs(call_options); @@ -92,14 +93,15 @@ std::shared_ptr GetTablesForSQLAllCatalogs( .AddFieldOfNulls(names.remarks_column, arrow::utf8()) .Build(); - return std::make_shared( - sql_client, call_options, flight_info, transformer, diagnostics, metadata_settings); + return std::make_shared(sql_client, client_options, call_options, + flight_info, transformer, diagnostics, + metadata_settings); } std::shared_ptr GetTablesForSQLAllDbSchemas( - const ColumnNames& names, FlightCallOptions& call_options, - FlightSqlClient& sql_client, const std::string* schema_name, - odbcabstraction::Diagnostics& diagnostics, + const ColumnNames& names, FlightClientOptions& client_options, + FlightCallOptions& call_options, FlightSqlClient& sql_client, + const std::string* schema_name, odbcabstraction::Diagnostics& diagnostics, const odbcabstraction::MetadataSettings& metadata_settings) { Result> result = sql_client.GetDbSchemas(call_options, nullptr, schema_name); @@ -119,13 +121,15 @@ std::shared_ptr GetTablesForSQLAllDbSchemas( .AddFieldOfNulls(names.remarks_column, arrow::utf8()) .Build(); - return std::make_shared( - sql_client, call_options, flight_info, transformer, diagnostics, metadata_settings); + return std::make_shared(sql_client, client_options, call_options, + flight_info, transformer, diagnostics, + metadata_settings); } std::shared_ptr GetTablesForSQLAllTableTypes( - const ColumnNames& names, FlightCallOptions& call_options, - FlightSqlClient& sql_client, odbcabstraction::Diagnostics& diagnostics, + const ColumnNames& names, FlightClientOptions& client_options, + FlightCallOptions& call_options, FlightSqlClient& sql_client, + odbcabstraction::Diagnostics& diagnostics, const odbcabstraction::MetadataSettings& metadata_settings) { Result> result = sql_client.GetTableTypes(call_options); @@ -144,15 +148,16 @@ std::shared_ptr GetTablesForSQLAllTableTypes( .AddFieldOfNulls(names.remarks_column, arrow::utf8()) .Build(); - return std::make_shared( - sql_client, call_options, flight_info, transformer, diagnostics, metadata_settings); + return std::make_shared(sql_client, client_options, call_options, + flight_info, transformer, diagnostics, + metadata_settings); } std::shared_ptr GetTablesForGenericUse( - const ColumnNames& names, FlightCallOptions& call_options, - FlightSqlClient& sql_client, const std::string* catalog_name, - const std::string* schema_name, const std::string* table_name, - const std::vector& table_types, + const ColumnNames& names, FlightClientOptions& client_options, + FlightCallOptions& call_options, FlightSqlClient& sql_client, + const std::string* catalog_name, const std::string* schema_name, + const std::string* table_name, const std::vector& table_types, odbcabstraction::Diagnostics& diagnostics, const odbcabstraction::MetadataSettings& metadata_settings) { Result> result = sql_client.GetTables( @@ -173,8 +178,9 @@ std::shared_ptr GetTablesForGenericUse( .AddFieldOfNulls(names.remarks_column, arrow::utf8()) .Build(); - return std::make_shared( - sql_client, call_options, flight_info, transformer, diagnostics, metadata_settings); + return std::make_shared(sql_client, client_options, call_options, + flight_info, transformer, diagnostics, + metadata_settings); } } // namespace flight_sql diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.h index 8f0dc5fef6da..0f5ac461f3ff 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.h @@ -30,6 +30,7 @@ namespace driver { namespace flight_sql { using arrow::flight::FlightCallOptions; +using arrow::flight::FlightClientOptions; using arrow::flight::sql::FlightSqlClient; using odbcabstraction::MetadataSettings; using odbcabstraction::ResultSet; @@ -46,26 +47,28 @@ void ParseTableTypes(const std::string& table_type, std::vector& table_types); std::shared_ptr GetTablesForSQLAllCatalogs( - const ColumnNames& column_names, FlightCallOptions& call_options, - FlightSqlClient& sql_client, odbcabstraction::Diagnostics& diagnostics, + const ColumnNames& column_names, FlightClientOptions& client_options, + FlightCallOptions& call_options, FlightSqlClient& sql_client, + odbcabstraction::Diagnostics& diagnostics, const odbcabstraction::MetadataSettings& metadata_settings); std::shared_ptr GetTablesForSQLAllDbSchemas( - const ColumnNames& column_names, FlightCallOptions& call_options, - FlightSqlClient& sql_client, const std::string* schema_name, - odbcabstraction::Diagnostics& diagnostics, + const ColumnNames& column_names, FlightClientOptions& client_options, + FlightCallOptions& call_options, FlightSqlClient& sql_client, + const std::string* schema_name, odbcabstraction::Diagnostics& diagnostics, const odbcabstraction::MetadataSettings& metadata_settings); std::shared_ptr GetTablesForSQLAllTableTypes( - const ColumnNames& column_names, FlightCallOptions& call_options, - FlightSqlClient& sql_client, odbcabstraction::Diagnostics& diagnostics, + const ColumnNames& column_names, FlightClientOptions& client_options, + FlightCallOptions& call_options, FlightSqlClient& sql_client, + odbcabstraction::Diagnostics& diagnostics, const odbcabstraction::MetadataSettings& metadata_settings); std::shared_ptr GetTablesForGenericUse( - const ColumnNames& column_names, FlightCallOptions& call_options, - FlightSqlClient& sql_client, const std::string* catalog_name, - const std::string* schema_name, const std::string* table_name, - const std::vector& table_types, + const ColumnNames& column_names, FlightClientOptions& client_options, + FlightCallOptions& call_options, FlightSqlClient& sql_client, + const std::string* catalog_name, const std::string* schema_name, + const std::string* table_name, const std::vector& table_types, odbcabstraction::Diagnostics& diagnostics, const odbcabstraction::MetadataSettings& metadata_settings); } // namespace flight_sql diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer.cc index 093a46dfe838..7da2d6ca89de 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer.cc @@ -21,38 +21,71 @@ namespace driver { namespace flight_sql { +using arrow::flight::FlightClient; using arrow::flight::FlightEndpoint; FlightStreamChunkBuffer::FlightStreamChunkBuffer( FlightSqlClient& flight_sql_client, + const arrow::flight::FlightClientOptions& client_options, const arrow::flight::FlightCallOptions& call_options, const std::shared_ptr& flight_info, size_t queue_capacity) : queue_(queue_capacity) { - // FIXME: Endpoint iteration should consider endpoints may be at different hosts for (const auto& endpoint : flight_info->endpoints()) { const arrow::flight::Ticket& ticket = endpoint.ticket; - auto result = flight_sql_client.DoGet(call_options, ticket); + arrow::Result> result; + std::shared_ptr temp_flight_sql_client; + auto endpoint_locations = endpoint.locations; + if (endpoint_locations.empty()) { + // list of Locations needs to be empty to proceed + result = flight_sql_client.DoGet(call_options, ticket); + } else { + // If it is non-empty, the driver should create a FlightSqlClient to connect to one + // of the specified Locations directly. + + // GH-47117: Currently a new FlightClient will be made for each partition that + // returns a non-empty Location, which is then disposed of. It may be better to + // cache clients because a server may report the same Locations. It would also be + // good to identify when the reported Location is the same as the original + // connection's Location and skip creating a FlightClient in that scenario. + + std::unique_ptr temp_flight_client; + ThrowIfNotOK(FlightClient::Connect(endpoint_locations[0], client_options) + .Value(&temp_flight_client)); + temp_flight_sql_client.reset(new FlightSqlClient(std::move(temp_flight_client))); + + result = temp_flight_sql_client->DoGet(call_options, ticket); + } + ThrowIfNotOK(result.status()); std::shared_ptr stream_reader_ptr(std::move(result.ValueOrDie())); - BlockingQueue>::Supplier supplier = [=] { + BlockingQueue, + std::shared_ptr>>::Supplier supplier = [=] { auto result = stream_reader_ptr->Next(); bool isNotOk = !result.ok(); bool isNotEmpty = result.ok() && (result.ValueOrDie().data != nullptr); - return boost::make_optional(isNotOk || isNotEmpty, std::move(result)); + // If result is valid, save the temp Flight SQL Client for future stream reader + // call. temp_flight_sql_client is intentionally null if the list of endpoint + // Locations is empty. + // After all data is fetched from reader, the temp client is closed. + return boost::make_optional( + isNotOk || isNotEmpty, + std::make_pair(std::move(result), temp_flight_sql_client)); }; queue_.AddProducer(std::move(supplier)); } } bool FlightStreamChunkBuffer::GetNext(FlightStreamChunk* chunk) { - Result result; - if (!queue_.Pop(&result)) { + std::pair, std::shared_ptr> + closeableEndpointStreamPair; + if (!queue_.Pop(&closeableEndpointStreamPair)) { return false; } + Result result = closeableEndpointStreamPair.first; if (!result.status().ok()) { Close(); throw odbcabstraction::DriverException(result.status().message()); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer.h index 4a84bcbede07..5d5616a4f027 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer.h @@ -17,9 +17,9 @@ #pragma once -#include -#include -#include +#include "arrow/flight/client.h" +#include "arrow/flight/sql/client.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/blocking_queue.h" namespace driver { namespace flight_sql { @@ -32,10 +32,12 @@ using arrow::flight::sql::FlightSqlClient; using driver::odbcabstraction::BlockingQueue; class FlightStreamChunkBuffer { - BlockingQueue> queue_; + BlockingQueue, std::shared_ptr>> + queue_; public: FlightStreamChunkBuffer(FlightSqlClient& flight_sql_client, + const arrow::flight::FlightClientOptions& client_options, const arrow::flight::FlightCallOptions& call_options, const std::shared_ptr& flight_info, size_t queue_capacity = 5); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer_test.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer_test.cc new file mode 100644 index 000000000000..6857b53f5c25 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer_test.cc @@ -0,0 +1,137 @@ +// 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/array.h" + +#include "arrow/testing/gtest_util.h" + +#include "arrow/flight/sql/odbc/flight_sql/flight_sql_stream_chunk_buffer.h" +#include "arrow/flight/sql/odbc/flight_sql/json_converter.h" +#include "arrow/flight/test_flight_server.h" +#include "arrow/flight/test_util.h" +#include "gtest/gtest.h" + +namespace driver { +namespace flight_sql { + +using arrow::Array; +using arrow::flight::FlightCallOptions; +using arrow::flight::FlightClientOptions; +using arrow::flight::FlightDescriptor; +using arrow::flight::FlightEndpoint; +using arrow::flight::Location; +using arrow::flight::Ticket; +using arrow::flight::sql::FlightSqlClient; + +class FlightStreamChunkBufferTest : public ::testing::Test { + // Sets up two mock servers for each test case. + // This is for testing endpoint iteration only. + + protected: + void SetUp() override { + // Set up server 1 + server1 = std::make_shared(); + ASSERT_OK_AND_ASSIGN(auto location1, Location::ForGrpcTcp("0.0.0.0", 0)); + arrow::flight::FlightServerOptions options1(location1); + ASSERT_OK(server1->Init(options1)); + ASSERT_OK_AND_ASSIGN(server_location1, + Location::ForGrpcTcp("localhost", server1->port())); + + // Set up server 2 + server2 = std::make_shared(); + ASSERT_OK_AND_ASSIGN(auto location2, Location::ForGrpcTcp("0.0.0.0", 0)); + arrow::flight::FlightServerOptions options2(location2); + ASSERT_OK(server2->Init(options2)); + ASSERT_OK_AND_ASSIGN(server_location2, + Location::ForGrpcTcp("localhost", server2->port())); + + // Make SQL Client that is connected to server 1 + ASSERT_OK_AND_ASSIGN(auto client, arrow::flight::FlightClient::Connect(location1)); + sql_client.reset(new FlightSqlClient(std::move(client))); + } + + void TearDown() override { + ASSERT_OK(server1->Shutdown()); + ASSERT_OK(server2->Shutdown()); + } + + public: + arrow::flight::Location server_location1; + std::shared_ptr server1; + arrow::flight::Location server_location2; + std::shared_ptr server2; + std::shared_ptr sql_client; +}; + +FlightInfo MultipleEndpointsFlightInfo(Location location1, Location location2) { + // Sever will generate random data for `ticket-ints-1` + FlightEndpoint endpoint1({Ticket{"ticket-ints-1"}, {location1}, std::nullopt, {}}); + FlightEndpoint endpoint2({Ticket{"ticket-ints-1"}, {location2}, std::nullopt, {}}); + + FlightDescriptor descr1{FlightDescriptor::PATH, "", {"examples", "ints"}}; + + auto schema1 = arrow::flight::ExampleIntSchema(); + + return arrow::flight::MakeFlightInfo(*schema1, descr1, {endpoint1, endpoint2}, 1000, + 100000, false, ""); +} + +void verifyArraysContainIntsOnly(std::shared_ptr intArray) { + for (int64_t i = 0; i < intArray->length(); ++i) { + // null values are accepted + if (!intArray->IsNull(i)) { + auto scalar_data = intArray->GetScalar(i).ValueOrDie(); + std::string scalar_str = ConvertToJson(*scalar_data); + ASSERT_TRUE(std::all_of(scalar_str.begin(), scalar_str.end(), ::isdigit)); + } + } +} + +TEST_F(FlightStreamChunkBufferTest, TestMultipleEndpointsInt) { + FlightClientOptions client_options = FlightClientOptions::Defaults(); + FlightCallOptions options; + FlightInfo info = MultipleEndpointsFlightInfo(server_location1, server_location2); + std::shared_ptr info_ptr = std::make_shared(info); + + FlightStreamChunkBuffer chunk_buffer(*sql_client, client_options, options, info_ptr); + + FlightStreamChunk current_chunk; + + // Server returns 5 batch of results from each endpoints. + // Each batch contains 8 columns + int num_chunks = 0; + while (chunk_buffer.GetNext(¤t_chunk)) { + num_chunks++; + + int num_cols = current_chunk.data->num_columns(); + EXPECT_EQ(num_cols, 8); + + for (int i = 0; i < num_cols; i++) { + auto array = current_chunk.data->column(i); + // Each array has random length + EXPECT_GT(array->length(), 0); + + verifyArraysContainIntsOnly(array); + } + } + + // Verify 5 batches of data is returned by each of the two endpoints. + // In total 10 batches should be returned. + EXPECT_EQ(num_chunks, 10); +} +} // namespace flight_sql +} // namespace driver diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/get_info_cache.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/get_info_cache.cc index d18322badbe3..1e4cdbeb65de 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/get_info_cache.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/get_info_cache.cc @@ -204,15 +204,20 @@ inline void SetDefaultIfMissing( namespace driver { namespace flight_sql { using arrow::flight::FlightCallOptions; +using arrow::flight::FlightClientOptions; using arrow::flight::sql::FlightSqlClient; using arrow::flight::sql::SqlInfoOptions; using driver::odbcabstraction::Connection; using driver::odbcabstraction::DriverException; -GetInfoCache::GetInfoCache(FlightCallOptions& call_options, +GetInfoCache::GetInfoCache(FlightClientOptions& client_options, + FlightCallOptions& call_options, std::unique_ptr& client, const std::string& driver_version) - : call_options_(call_options), sql_client_(client), has_server_info_(false) { + : client_options_(client_options), + call_options_(call_options), + sql_client_(client), + has_server_info_(false) { info_[SQL_DRIVER_NAME] = "Arrow Flight ODBC Driver"; info_[SQL_DRIVER_VER] = ConvertToDBMSVer(driver_version); @@ -294,7 +299,8 @@ bool GetInfoCache::LoadInfoFromServer() { arrow::Result> result = sql_client_->GetSqlInfo(call_options_, {}); ThrowIfNotOK(result.status()); - FlightStreamChunkBuffer chunk_iter(*sql_client_, call_options_, result.ValueOrDie()); + FlightStreamChunkBuffer chunk_iter(*sql_client_, client_options_, call_options_, + result.ValueOrDie()); FlightStreamChunk chunk; bool supports_correlation_name = false; @@ -1173,6 +1179,7 @@ void GetInfoCache::LoadDefaultsForMissingEntries() { SetDefaultIfMissing(info_, SQL_CONVERT_DECIMAL, static_cast(0)); SetDefaultIfMissing(info_, SQL_CONVERT_DOUBLE, static_cast(0)); SetDefaultIfMissing(info_, SQL_CONVERT_FLOAT, static_cast(0)); + SetDefaultIfMissing(info_, SQL_CONVERT_FUNCTIONS, static_cast(0)); SetDefaultIfMissing(info_, SQL_CONVERT_GUID, static_cast(0)); SetDefaultIfMissing(info_, SQL_CONVERT_INTEGER, static_cast(0)); SetDefaultIfMissing(info_, SQL_CONVERT_INTERVAL_YEAR_MONTH, static_cast(0)); @@ -1251,6 +1258,7 @@ void GetInfoCache::LoadDefaultsForMissingEntries() { SetDefaultIfMissing(info_, SQL_MAX_COLUMNS_IN_ORDER_BY, static_cast(0)); SetDefaultIfMissing(info_, SQL_MAX_COLUMNS_IN_SELECT, static_cast(0)); SetDefaultIfMissing(info_, SQL_MAX_COLUMNS_IN_TABLE, static_cast(0)); + SetDefaultIfMissing(info_, SQL_MAX_CONCURRENT_ACTIVITIES, static_cast(0)); SetDefaultIfMissing(info_, SQL_MAX_CURSOR_NAME_LEN, static_cast(0)); SetDefaultIfMissing(info_, SQL_MAX_DRIVER_CONNECTIONS, static_cast(0)); SetDefaultIfMissing(info_, SQL_MAX_IDENTIFIER_LEN, static_cast(65535)); @@ -1270,6 +1278,7 @@ void GetInfoCache::LoadDefaultsForMissingEntries() { SetDefaultIfMissing(info_, SQL_OJ_CAPABILITIES, static_cast(SQL_OJ_LEFT | SQL_OJ_RIGHT | SQL_OJ_FULL)); SetDefaultIfMissing(info_, SQL_ORDER_BY_COLUMNS_IN_SELECT, "Y"); + SetDefaultIfMissing(info_, SQL_OUTER_JOINS, "N"); SetDefaultIfMissing(info_, SQL_PROCEDURE_TERM, ""); SetDefaultIfMissing(info_, SQL_PROCEDURES, "N"); SetDefaultIfMissing(info_, SQL_QUOTED_IDENTIFIER_CASE, @@ -1278,6 +1287,7 @@ void GetInfoCache::LoadDefaultsForMissingEntries() { SetDefaultIfMissing(info_, SQL_SCHEMA_USAGE, static_cast(SQL_SU_DML_STATEMENTS)); SetDefaultIfMissing(info_, SQL_SEARCH_PATTERN_ESCAPE, "\\"); + SetDefaultIfMissing(info_, SQL_SPECIAL_CHARACTERS, ""); SetDefaultIfMissing( info_, SQL_SERVER_NAME, "Arrow Flight SQL Server"); // This might actually need to be the hostname. @@ -1332,6 +1342,16 @@ void GetInfoCache::LoadDefaultsForMissingEntries() { SQL_FN_TSI_FRAC_SECOND | SQL_FN_TSI_SECOND | SQL_FN_TSI_MINUTE | SQL_FN_TSI_HOUR | SQL_FN_TSI_DAY | SQL_FN_TSI_WEEK | SQL_FN_TSI_MONTH | SQL_FN_TSI_QUARTER | SQL_FN_TSI_YEAR)); + SetDefaultIfMissing( + info_, SQL_TIMEDATE_FUNCTIONS, + static_cast( + SQL_FN_TD_CURRENT_DATE | SQL_FN_TD_CURRENT_TIME | SQL_FN_TD_CURRENT_TIMESTAMP | + SQL_FN_TD_CURDATE | SQL_FN_TD_CURTIME | SQL_FN_TD_DAYNAME | + SQL_FN_TD_DAYOFMONTH | SQL_FN_TD_DAYOFWEEK | SQL_FN_TD_DAYOFYEAR | + SQL_FN_TD_EXTRACT | SQL_FN_TD_HOUR | SQL_FN_TD_MINUTE | SQL_FN_TD_MONTH | + SQL_FN_TD_MONTHNAME | SQL_FN_TD_NOW | SQL_FN_TD_QUARTER | SQL_FN_TD_SECOND | + SQL_FN_TD_TIMESTAMPADD | SQL_FN_TD_TIMESTAMPDIFF | SQL_FN_TD_WEEK | + SQL_FN_TD_YEAR)); SetDefaultIfMissing(info_, SQL_UNION, static_cast(SQL_U_UNION | SQL_U_UNION_ALL)); SetDefaultIfMissing(info_, SQL_XOPEN_CLI_YEAR, "1995"); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/get_info_cache.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/get_info_cache.h index a54dda2e13bc..547fb1cdf286 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/get_info_cache.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/get_info_cache.h @@ -17,12 +17,12 @@ #pragma once -#include -#include #include #include #include #include +#include "arrow/flight/sql/client.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h" namespace driver { namespace flight_sql { @@ -30,13 +30,15 @@ namespace flight_sql { class GetInfoCache { private: std::unordered_map info_; + arrow::flight::FlightClientOptions& client_options_; arrow::flight::FlightCallOptions& call_options_; std::unique_ptr& sql_client_; std::mutex mutex_; std::atomic has_server_info_; public: - GetInfoCache(arrow::flight::FlightCallOptions& call_options, + GetInfoCache(arrow::flight::FlightClientOptions& client_options, + arrow::flight::FlightCallOptions& call_options, std::unique_ptr& client, const std::string& driver_version); void SetProperty(uint16_t property, driver::odbcabstraction::Connection::Info value); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h index 69fa8a8696c5..c94cc5b78328 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h @@ -46,27 +46,21 @@ class Configuration { */ ~Configuration(); - /** - * Convert configure to connect string. - * - * @return Connect string. - */ - std::string ToConnectString() const; - void LoadDefaults(); void LoadDsn(const std::string& dsn); void Clear(); bool IsSet(const std::string_view& key) const; const std::string& Get(const std::string_view& key) const; + void Set(const std::string_view& key, const std::wstring& wValue); void Set(const std::string_view& key, const std::string& value); - + void Emplace(const std::string_view& key, std::string&& value); /** * Get properties map. */ const driver::odbcabstraction::Connection::ConnPropertyMap& GetProperties() const; - std::vector GetCustomKeys() const; + std::vector GetCustomKeys() const; private: driver::odbcabstraction::Connection::ConnPropertyMap properties; diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/flight_sql_driver.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/flight_sql_driver.h index 88460cdf5b2f..48f2a16416ab 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/flight_sql_driver.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/flight_sql_driver.h @@ -17,8 +17,8 @@ #pragma once -#include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/driver.h" namespace driver { namespace flight_sql { diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/add_property_window.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/add_property_window.h index 01d93829a46b..b7a8016447c1 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/add_property_window.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/add_property_window.h @@ -70,7 +70,7 @@ class AddPropertyWindow : public CustomWindow { * * @return true if the dialog was OK'd, false otherwise. */ - bool GetProperty(std::string& key, std::string& value); + bool GetProperty(std::wstring& key, std::wstring& value); private: /** @@ -97,9 +97,9 @@ class AddPropertyWindow : public CustomWindow { std::unique_ptr valueEdit; - std::string key; + std::wstring key; - std::string value; + std::wstring value; /** Window width. */ int width; diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/custom_window.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/custom_window.h index 0fc3737ed8bc..649f0ef6547b 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/custom_window.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/custom_window.h @@ -65,7 +65,7 @@ class CustomWindow : public Window { * @param className Window class name. * @param title Window title. */ - CustomWindow(Window* parent, const char* className, const char* title); + CustomWindow(Window* parent, const wchar_t* className, const wchar_t* title); /** * Destructor. diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/window.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/window.h index e56ad88dec65..596ff47c577a 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/window.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/window.h @@ -44,7 +44,7 @@ class Window { * @param className Window class name. * @param title Window title. */ - Window(Window* parent, const char* className, const char* title); + Window(Window* parent, const wchar_t* className, const wchar_t* title); /** * Constructor for the existing window. @@ -102,7 +102,7 @@ class Window { * @return Auto pointer containing new window. */ std::unique_ptr CreateGroupBox(int posX, int posY, int sizeX, int sizeY, - const char* title, int id); + const wchar_t* title, int id); /** * Create child label window. @@ -116,7 +116,7 @@ class Window { * @return Auto pointer containing new window. */ std::unique_ptr CreateLabel(int posX, int posY, int sizeX, int sizeY, - const char* title, int id); + const wchar_t* title, int id); /** * Create child Edit window. @@ -131,7 +131,7 @@ class Window { * @return Auto pointer containing new window. */ std::unique_ptr CreateEdit(int posX, int posY, int sizeX, int sizeY, - const char* title, int id, int style = 0); + const wchar_t* title, int id, int style = 0); /** * Create child button window. @@ -146,7 +146,7 @@ class Window { * @return Auto pointer containing new window. */ std::unique_ptr CreateButton(int posX, int posY, int sizeX, int sizeY, - const char* title, int id, int style = 0); + const wchar_t* title, int id, int style = 0); /** * Create child CheckBox window. @@ -161,7 +161,7 @@ class Window { * @return Auto pointer containing new window. */ std::unique_ptr CreateCheckBox(int posX, int posY, int sizeX, int sizeY, - const char* title, int id, bool state); + const wchar_t* title, int id, bool state); /** * Create child ComboBox window. @@ -175,7 +175,7 @@ class Window { * @return Auto pointer containing new window. */ std::unique_ptr CreateComboBox(int posX, int posY, int sizeX, int sizeY, - const char* title, int id); + const wchar_t* title, int id); /** * Show window. @@ -201,15 +201,15 @@ class Window { void SetVisible(bool isVisible); - void ListAddColumn(const std::string& name, int index, int width); + void ListAddColumn(const std::wstring& name, int index, int width); - void ListAddItem(const std::vector& items); + void ListAddItem(const std::vector& items); void ListDeleteSelectedItem(); - std::vector > ListGetAll(); + std::vector > ListGetAll(); - void AddTab(const std::string& name, int index); + void AddTab(const std::wstring& name, int index); bool IsTextEmpty() const; @@ -218,14 +218,14 @@ class Window { * * @param text Text. */ - void GetText(std::string& text) const; + void GetText(std::wstring& text) const; /** * Set window text. * * @param text Text. */ - void SetText(const std::string& text) const; + void SetText(const std::wstring& text) const; /** * Get CheckBox state. @@ -246,7 +246,7 @@ class Window { * * @param str String. */ - void AddString(const std::string& str); + void AddString(const std::wstring& str); /** * Set current ComboBox selection. @@ -285,10 +285,10 @@ class Window { void SetHandle(HWND value) { handle = value; } /** Window class name. */ - std::string className; + std::wstring className; /** Window title. */ - std::string title; + std::wstring title; /** Window handle. */ HWND handle; diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/json_converter.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/json_converter.h index de466af4f77d..83809265df4a 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/json_converter.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/json_converter.h @@ -17,8 +17,8 @@ #pragma once -#include #include +#include "arrow/type_fwd.h" namespace driver { namespace flight_sql { diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/main.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/main.cc index e112fdf67c08..aaf267cc268e 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/main.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/main.cc @@ -43,7 +43,7 @@ using driver::odbcabstraction::Statement; void TestBindColumn(const std::shared_ptr& connection) { const std::shared_ptr& statement = connection->CreateStatement(); - statement->Execute("SELECT IncidntNum, Category FROM \"@dremio\".Test LIMIT 10"); + statement->Execute("SELECT IncidntNum, Category FROM \"@apache\".Test LIMIT 10"); const std::shared_ptr& result_set = statement->GetResultSet(); @@ -105,7 +105,7 @@ void TestBindColumnBigInt(const std::shared_ptr& connection) { " SELECT CONVERT_TO_INTEGER(IncidntNum, 1, 1, 0) AS IncidntNum, " "Category\n" " FROM (\n" - " SELECT IncidntNum, Category FROM \"@dremio\".Test LIMIT 10\n" + " SELECT IncidntNum, Category FROM \"@apache\".Test LIMIT 10\n" " ) nested_0\n" ") nested_0"); @@ -202,11 +202,11 @@ int main() { driver.CreateConnection(driver::odbcabstraction::V_3); Connection::ConnPropertyMap properties = { - {FlightSqlConnection::HOST, std::string("automaster.drem.io")}, - {FlightSqlConnection::PORT, std::string("32010")}, - {FlightSqlConnection::USER, std::string("dremio")}, - {FlightSqlConnection::PASSWORD, std::string("dremio123")}, - {FlightSqlConnection::USE_ENCRYPTION, std::string("false")}, + {std::string(FlightSqlConnection::HOST), std::string("automaster.apache")}, + {std::string(FlightSqlConnection::PORT), std::string("32010")}, + {std::string(FlightSqlConnection::USER), std::string("apache")}, + {std::string(FlightSqlConnection::PASSWORD), std::string("apache123")}, + {std::string(FlightSqlConnection::USE_ENCRYPTION), std::string("false")}, }; std::vector missing_attr; connection->Connect(properties, missing_attr); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/record_batch_transformer.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/record_batch_transformer.h index 261b8c1d7c04..15c482cc6310 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/record_batch_transformer.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/record_batch_transformer.h @@ -17,9 +17,9 @@ #pragma once -#include -#include #include +#include "arrow/flight/client.h" +#include "arrow/type.h" namespace driver { namespace flight_sql { diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/scalar_function_reporter.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/scalar_function_reporter.h index 5c2ae06cdbac..fd6abf6420e0 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/scalar_function_reporter.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/scalar_function_reporter.h @@ -17,7 +17,7 @@ #pragma once -#include +#include "arrow/type.h" namespace driver { namespace flight_sql { diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/system_dsn.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/system_dsn.cc index 504b62a81ebb..f0006b36c9a4 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/system_dsn.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/system_dsn.cc @@ -15,75 +15,30 @@ // specific language governing permissions and limitations // under the License. -// platform.h includes windows.h, so it needs to be included -// before winuser.h -#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" +#include "arrow/flight/sql/odbc/flight_sql/system_dsn.h" -#include -#include #include "arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h" #include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h" -#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/connection_string_parser.h" -#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/dsn_configuration_window.h" -#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/window.h" -#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" +#include "arrow/result.h" +#include "arrow/util/utf8.h" #include -#include -#include #include using driver::flight_sql::FlightSqlConnection; using driver::flight_sql::config::Configuration; -using driver::flight_sql::config::ConnectionStringParser; -using driver::flight_sql::config::DsnConfigurationWindow; -using driver::flight_sql::config::Result; -using driver::flight_sql::config::Window; - -BOOL CALLBACK ConfigDriver(HWND hwndParent, WORD fRequest, LPCSTR lpszDriver, - LPCSTR lpszArgs, LPSTR lpszMsg, WORD cbMsgMax, - WORD* pcbMsgOut) { - return false; -} - -bool DisplayConnectionWindow(void* windowParent, Configuration& config) { - HWND hwndParent = (HWND)windowParent; - - if (!hwndParent) return true; - - try { - Window parent(hwndParent); - DsnConfigurationWindow window(&parent, config); - - window.Create(); - - window.Show(); - window.Update(); - - return ProcessMessages(window) == Result::OK; - } catch (driver::odbcabstraction::DriverException& err) { - std::stringstream buf; - buf << "Message: " << err.GetMessageText() << ", Code: " << err.GetNativeError(); - std::string message = buf.str(); - MessageBox(NULL, message.c_str(), "Error!", MB_ICONEXCLAMATION | MB_OK); - - SQLPostInstallerError(err.GetNativeError(), err.GetMessageText().c_str()); - } - - return false; -} void PostLastInstallerError() { #define BUFFER_SIZE (1024) DWORD code; - char msg[BUFFER_SIZE]; + wchar_t msg[BUFFER_SIZE]; SQLInstallerError(1, &code, msg, BUFFER_SIZE, NULL); - std::stringstream buf; - buf << "Message: \"" << msg << "\", Code: " << code; - std::string errorMsg = buf.str(); + std::wstringstream buf; + buf << L"Message: \"" << msg << L"\", Code: " << code; + std::wstring errorMsg = buf.str(); - MessageBox(NULL, errorMsg.c_str(), "Error!", MB_ICONEXCLAMATION | MB_OK); + MessageBox(NULL, errorMsg.c_str(), L"Error!", MB_ICONEXCLAMATION | MB_OK); SQLPostInstallerError(code, errorMsg.c_str()); } @@ -93,7 +48,7 @@ void PostLastInstallerError() { * @param dsn DSN name. * @return True on success and false on fail. */ -bool UnregisterDsn(const std::string& dsn) { +bool UnregisterDsn(const std::wstring& dsn) { if (SQLRemoveDSNFromIni(dsn.c_str())) { return true; } @@ -109,10 +64,11 @@ bool UnregisterDsn(const std::string& dsn) { * @param driver Driver. * @return True on success and false on fail. */ -bool RegisterDsn(const Configuration& config, LPCSTR driver) { +bool RegisterDsn(const Configuration& config, LPCWSTR driver) { const std::string& dsn = config.Get(FlightSqlConnection::DSN); + std::wstring wDsn = arrow::util::UTF8ToWideString(dsn).ValueOr(L""); - if (!SQLWriteDSNToIni(dsn.c_str(), driver)) { + if (!SQLWriteDSNToIni(wDsn.c_str(), driver)) { PostLastInstallerError(); return false; } @@ -125,9 +81,10 @@ bool RegisterDsn(const Configuration& config, LPCSTR driver) { continue; } - std::string key_str = std::string(key); - if (!SQLWritePrivateProfileString(dsn.c_str(), key_str.c_str(), it->second.c_str(), - "ODBC.INI")) { + std::wstring wKey = arrow::util::UTF8ToWideString(key).ValueOr(L""); + std::wstring wValue = arrow::util::UTF8ToWideString(it->second).ValueOr(L""); + if (!SQLWritePrivateProfileString(wDsn.c_str(), wKey.c_str(), wValue.c_str(), + L"ODBC.INI")) { PostLastInstallerError(); return false; } @@ -135,45 +92,3 @@ bool RegisterDsn(const Configuration& config, LPCSTR driver) { return true; } - -BOOL INSTAPI ConfigDSN(HWND hwndParent, WORD req, LPCSTR driver, LPCSTR attributes) { - Configuration config; - ConnectionStringParser parser(config); - parser.ParseConfigAttributes(attributes); - - switch (req) { - case ODBC_ADD_DSN: { - config.LoadDefaults(); - if (!DisplayConnectionWindow(hwndParent, config) || !RegisterDsn(config, driver)) - return FALSE; - - break; - } - - case ODBC_CONFIG_DSN: { - const std::string& dsn = config.Get(FlightSqlConnection::DSN); - if (!SQLValidDSN(dsn.c_str())) return FALSE; - - Configuration loaded(config); - loaded.LoadDsn(dsn); - - if (!DisplayConnectionWindow(hwndParent, loaded) || !UnregisterDsn(dsn.c_str()) || - !RegisterDsn(loaded, driver)) - return FALSE; - - break; - } - - case ODBC_REMOVE_DSN: { - const std::string& dsn = config.Get(FlightSqlConnection::DSN); - if (!SQLValidDSN(dsn.c_str()) || !UnregisterDsn(dsn)) return FALSE; - - break; - } - - default: - return FALSE; - } - - return TRUE; -} diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/system_dsn.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/system_dsn.h new file mode 100644 index 000000000000..1ac9b4d9b804 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/system_dsn.h @@ -0,0 +1,65 @@ +// 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. + +// platform.h includes windows.h, so it needs to be included first +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" + +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h" + +using driver::flight_sql::config::Configuration; +using driver::odbcabstraction::Connection; + +#if defined _WIN32 || defined _WIN64 +/** + * Display connection window for user to configure connection parameters. + * + * @param windowParent Parent window handle. + * @param config Output configuration. + * @return True on success and false on fail. + */ +bool DisplayConnectionWindow(void* windowParent, Configuration& config); + +/** + * For SQLDriverConnect. + * Display connection window for user to configure connection parameters. + * + * @param windowParent Parent window handle. + * @param config Output configuration, presumed to be empty, it will be using values from + * properties. + * @param properties Output properties. + * @return True on success and false on fail. + */ +bool DisplayConnectionWindow(void* windowParent, Configuration& config, + Connection::ConnPropertyMap& properties); +#endif + +/** + * Register DSN with specified configuration. + * + * @param config Configuration. + * @param driver Driver. + * @return True on success and false on fail. + */ +bool RegisterDsn(const Configuration& config, LPCWSTR driver); + +/** + * Unregister specified DSN. + * + * @param dsn DSN name. + * @return True on success and false on fail. + */ +bool UnregisterDsn(const std::wstring& dsn); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/system_trust_store.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/system_trust_store.cc index 67db1fc35be6..ebc8fd90adf8 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/system_trust_store.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/system_trust_store.cc @@ -15,6 +15,9 @@ // specific language governing permissions and limitations // under the License. +#include "arrow/result.h" +#include "arrow/util/utf8.h" + #include "arrow/flight/sql/odbc/flight_sql/system_trust_store.h" #if defined _WIN32 || defined _WIN64 @@ -32,18 +35,20 @@ std::string SystemTrustStore::GetNext() const { CryptBinaryToString(p_context_->pbCertEncoded, p_context_->cbCertEncoded, CRYPT_STRING_BASE64HEADER, nullptr, &size); - std::string cert; - cert.resize(size); + std::wstring wCert; + wCert.resize(size); CryptBinaryToString(p_context_->pbCertEncoded, p_context_->cbCertEncoded, - CRYPT_STRING_BASE64HEADER, &cert[0], &size); - cert.resize(size); + CRYPT_STRING_BASE64HEADER, &wCert[0], &size); + wCert.resize(size); + + std::string cert = arrow::util::WideStringToUTF8(wCert).ValueOr(""); return cert; } bool SystemTrustStore::SystemHasStore() { return h_store_ != nullptr; } -SystemTrustStore::SystemTrustStore(const char* store) +SystemTrustStore::SystemTrustStore(const wchar_t* store) : stores_(store), h_store_(CertOpenSystemStore(NULL, store)), p_context_(nullptr) {} SystemTrustStore::~SystemTrustStore() { diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/system_trust_store.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/system_trust_store.h index 71175b097094..0ff3adc2f482 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/system_trust_store.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/system_trust_store.h @@ -24,6 +24,9 @@ # include # include + +# include + # include # include @@ -38,12 +41,12 @@ namespace flight_sql { /// https://github.com/apache/drill/blob/master/contrib/native/client/src/clientlib/wincert.ipp. class SystemTrustStore { private: - const char* stores_; + const wchar_t* stores_; HCERTSTORE h_store_; PCCERT_CONTEXT p_context_; public: - explicit SystemTrustStore(const char* store); + explicit SystemTrustStore(const wchar_t* store); ~SystemTrustStore(); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/add_property_window.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/add_property_window.cc index 64cc1797f7ed..15799c1f9a28 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/add_property_window.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/add_property_window.cc @@ -24,7 +24,7 @@ #include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" #include "ui/custom_window.h" #include "ui/window.h" @@ -33,7 +33,7 @@ namespace flight_sql { namespace config { AddPropertyWindow::AddPropertyWindow(Window* parent) - : CustomWindow(parent, "AddProperty", "Add Property"), + : CustomWindow(parent, L"AddProperty", L"Add Property"), width(300), height(120), accepted(false), @@ -69,7 +69,7 @@ void AddPropertyWindow::Create() { } } -bool AddPropertyWindow::GetProperty(std::string& key, std::string& value) { +bool AddPropertyWindow::GetProperty(std::wstring& key, std::wstring& value) { if (accepted) { key = this->key; value = this->value; @@ -87,10 +87,10 @@ void AddPropertyWindow::OnCreate() { int cancelPosX = width - MARGIN - BUTTON_WIDTH; int okPosX = cancelPosX - INTERVAL - BUTTON_WIDTH; - okButton = CreateButton(okPosX, groupPosY, BUTTON_WIDTH, BUTTON_HEIGHT, "Ok", + okButton = CreateButton(okPosX, groupPosY, BUTTON_WIDTH, BUTTON_HEIGHT, L"Ok", ChildId::OK_BUTTON, BS_DEFPUSHBUTTON); cancelButton = CreateButton(cancelPosX, groupPosY, BUTTON_WIDTH, BUTTON_HEIGHT, - "Cancel", ChildId::CANCEL_BUTTON); + L"Cancel", ChildId::CANCEL_BUTTON); isInitialized = true; CheckEnableOk(); } @@ -104,15 +104,15 @@ int AddPropertyWindow::CreateEdits(int posX, int posY, int sizeX) { int rowPos = posY; labels.push_back( - CreateLabel(posX, rowPos, LABEL_WIDTH, ROW_HEIGHT, "Key:", ChildId::KEY_LABEL)); - keyEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, "", ChildId::KEY_EDIT); + CreateLabel(posX, rowPos, LABEL_WIDTH, ROW_HEIGHT, L"Key:", ChildId::KEY_LABEL)); + keyEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, L"", ChildId::KEY_EDIT); rowPos += INTERVAL + ROW_HEIGHT; - labels.push_back( - CreateLabel(posX, rowPos, LABEL_WIDTH, ROW_HEIGHT, "Value:", ChildId::VALUE_LABEL)); + labels.push_back(CreateLabel(posX, rowPos, LABEL_WIDTH, ROW_HEIGHT, L"Value:", + ChildId::VALUE_LABEL)); valueEdit = - CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, "", ChildId::VALUE_EDIT); + CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, L"", ChildId::VALUE_EDIT); rowPos += INTERVAL + ROW_HEIGHT; diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/custom_window.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/custom_window.cc index 5443ea0ec8d2..bde7967c7e99 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/custom_window.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/custom_window.cc @@ -17,15 +17,16 @@ // platform.h includes windows.h, so it needs to be included // before Windowsx.h and commctrl.h -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" #include #include #include +#include #include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" #include "ui/custom_window.h" namespace driver { @@ -53,7 +54,7 @@ LRESULT CALLBACK CustomWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, switch (msg) { case WM_NCCREATE: { - _ASSERT(lParam != NULL); + assert(lParam != NULL); CREATESTRUCT* createStruct = reinterpret_cast(lParam); @@ -65,7 +66,7 @@ LRESULT CALLBACK CustomWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, } case WM_CREATE: { - _ASSERT(window != NULL); + assert(window != NULL); window->SetHandle(hwnd); @@ -83,7 +84,7 @@ LRESULT CALLBACK CustomWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, return DefWindowProc(hwnd, msg, wParam, lParam); } -CustomWindow::CustomWindow(Window* parent, const char* className, const char* title) +CustomWindow::CustomWindow(Window* parent, const wchar_t* className, const wchar_t* title) : Window(parent, className, title) { WNDCLASS wcx; diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/dsn_configuration_window.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/dsn_configuration_window.cc index 42741c5a3e56..c6a132714350 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/dsn_configuration_window.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/dsn_configuration_window.cc @@ -15,16 +15,19 @@ // specific language governing permissions and limitations // under the License. -#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/dsn_configuration_window.h" +#include "arrow/result.h" +#include "arrow/util/utf8.h" + #include "arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h" +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/dsn_configuration_window.h" #include #include -#include #include #include #include #include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/utils.h" #include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/add_property_window.h" @@ -55,7 +58,7 @@ namespace config { DsnConfigurationWindow::DsnConfigurationWindow(Window* parent, config::Configuration& config) - : CustomWindow(parent, "FlightConfigureDSN", "Configure Apache Arrow Flight SQL"), + : CustomWindow(parent, L"FlightConfigureDSN", L"Configure Apache Arrow Flight SQL"), width(480), height(375), config(config), @@ -94,8 +97,8 @@ void DsnConfigurationWindow::Create() { void DsnConfigurationWindow::OnCreate() { tabControl = CreateTabControl(ChildId::TAB_CONTROL); - tabControl->AddTab("Common", COMMON_TAB); - tabControl->AddTab("Advanced", ADVANCED_TAB); + tabControl->AddTab(L"Common", COMMON_TAB); + tabControl->AddTab(L"Advanced", ADVANCED_TAB); int groupPosY = 3 * MARGIN; int groupSizeY = width - 2 * MARGIN; @@ -118,11 +121,11 @@ void DsnConfigurationWindow::OnCreate() { int buttonPosY = std::max(commonGroupPosY, advancedGroupPosY); testButton = CreateButton(testPosX, buttonPosY, BUTTON_WIDTH + 20, BUTTON_HEIGHT, - "Test Connection", ChildId::TEST_CONNECTION_BUTTON); - okButton = CreateButton(okPosX, buttonPosY, BUTTON_WIDTH, BUTTON_HEIGHT, "Ok", + L"Test Connection", ChildId::TEST_CONNECTION_BUTTON); + okButton = CreateButton(okPosX, buttonPosY, BUTTON_WIDTH, BUTTON_HEIGHT, L"Ok", ChildId::OK_BUTTON); cancelButton = CreateButton(cancelPosX, buttonPosY, BUTTON_WIDTH, BUTTON_HEIGHT, - "Cancel", ChildId::CANCEL_BUTTON); + L"Cancel", ChildId::CANCEL_BUTTON); isInitialized = true; CheckEnableOk(); SelectTab(COMMON_TAB); @@ -138,31 +141,35 @@ int DsnConfigurationWindow::CreateConnectionSettingsGroup(int posX, int posY, in int rowPos = posY + 2 * INTERVAL; - const char* val = config.Get(FlightSqlConnection::DSN).c_str(); + std::string val = config.Get(FlightSqlConnection::DSN); + std::wstring wVal = arrow::util::UTF8ToWideString(val).ValueOr(L""); labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, - "Data Source Name:", ChildId::NAME_LABEL)); - nameEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, val, ChildId::NAME_EDIT); + L"Data Source Name:", ChildId::NAME_LABEL)); + nameEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, wVal.c_str(), + ChildId::NAME_EDIT); rowPos += INTERVAL + ROW_HEIGHT; - val = config.Get(FlightSqlConnection::HOST).c_str(); - labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, - "Host Name:", ChildId::SERVER_LABEL)); - serverEdit = - CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, val, ChildId::SERVER_EDIT); + val = config.Get(FlightSqlConnection::HOST); + wVal = arrow::util::UTF8ToWideString(val).ValueOr(L""); + labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, L"Host Name:", + ChildId::SERVER_LABEL)); + serverEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, wVal.c_str(), + ChildId::SERVER_EDIT); rowPos += INTERVAL + ROW_HEIGHT; - val = config.Get(FlightSqlConnection::PORT).c_str(); - labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, - "Port:", ChildId::PORT_LABEL)); - portEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, val, ChildId::PORT_EDIT, - ES_NUMBER); + val = config.Get(FlightSqlConnection::PORT); + wVal = arrow::util::UTF8ToWideString(val).ValueOr(L""); + labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, L"Port:", + ChildId::PORT_LABEL)); + portEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, wVal.c_str(), + ChildId::PORT_EDIT, ES_NUMBER); rowPos += INTERVAL + ROW_HEIGHT; connectionSettingsGroupBox = - CreateGroupBox(posX, posY, sizeX, rowPos - posY, "Connection settings", + CreateGroupBox(posX, posY, sizeX, rowPos - posY, L"Connection settings", ChildId::CONNECTION_SETTINGS_GROUP_BOX); return rowPos - posY; @@ -179,36 +186,39 @@ int DsnConfigurationWindow::CreateAuthSettingsGroup(int posX, int posY, int size int rowPos = posY + 2 * INTERVAL; labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, - "Authentication Type:", ChildId::AUTH_TYPE_LABEL)); + L"Authentication Type:", ChildId::AUTH_TYPE_LABEL)); authTypeComboBox = CreateComboBox(editPosX, rowPos, editSizeX, ROW_HEIGHT, - "Authentication Type:", ChildId::AUTH_TYPE_COMBOBOX); - authTypeComboBox->AddString("Basic Authentication"); - authTypeComboBox->AddString("Token Authentication"); + L"Authentication Type:", ChildId::AUTH_TYPE_COMBOBOX); + authTypeComboBox->AddString(L"Basic Authentication"); + authTypeComboBox->AddString(L"Token Authentication"); rowPos += INTERVAL + ROW_HEIGHT; - const char* val = config.Get(FlightSqlConnection::UID).c_str(); + std::string val = config.Get(FlightSqlConnection::UID); + std::wstring wVal = arrow::util::UTF8ToWideString(val).ValueOr(L""); - labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, - "User:", ChildId::USER_LABEL)); - userEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, val, ChildId::USER_EDIT); + labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, L"User:", + ChildId::USER_LABEL)); + userEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, wVal.c_str(), + ChildId::USER_EDIT); rowPos += INTERVAL + ROW_HEIGHT; - val = config.Get(FlightSqlConnection::PWD).c_str(); - labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, - "Password:", ChildId::PASSWORD_LABEL)); - passwordEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, val, + val = config.Get(FlightSqlConnection::PWD); + wVal = arrow::util::UTF8ToWideString(val).ValueOr(L""); + labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, L"Password:", + ChildId::PASSWORD_LABEL)); + passwordEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, wVal.c_str(), ChildId::USER_EDIT, ES_PASSWORD); rowPos += INTERVAL + ROW_HEIGHT; const auto& token = config.Get(FlightSqlConnection::TOKEN); - val = token.c_str(); + wVal = arrow::util::UTF8ToWideString(token).ValueOr(L""); labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, - "Authentication Token:", ChildId::AUTH_TOKEN_LABEL)); - authTokenEdit = - CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, val, ChildId::AUTH_TOKEN_EDIT); + L"Authentication Token:", ChildId::AUTH_TOKEN_LABEL)); + authTokenEdit = CreateEdit(editPosX, rowPos, editSizeX, ROW_HEIGHT, wVal.c_str(), + ChildId::AUTH_TOKEN_EDIT); authTokenEdit->SetEnabled(false); // Ensure the right elements are selected. @@ -218,7 +228,7 @@ int DsnConfigurationWindow::CreateAuthSettingsGroup(int posX, int posY, int size rowPos += INTERVAL + ROW_HEIGHT; authSettingsGroupBox = - CreateGroupBox(posX, posY, sizeX, rowPos - posY, "Authentication settings", + CreateGroupBox(posX, posY, sizeX, rowPos - posY, L"Authentication settings", ChildId::AUTH_SETTINGS_GROUP_BOX); return rowPos - posY; @@ -234,37 +244,38 @@ int DsnConfigurationWindow::CreateEncryptionSettingsGroup(int posX, int posY, in int rowPos = posY + 2 * INTERVAL; - const char* val = config.Get(FlightSqlConnection::USE_ENCRYPTION).c_str(); + std::string val = config.Get(FlightSqlConnection::USE_ENCRYPTION); const bool enableEncryption = driver::odbcabstraction::AsBool(val).value_or(true); labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, - "Use Encryption:", ChildId::ENABLE_ENCRYPTION_LABEL)); + L"Use Encryption:", ChildId::ENABLE_ENCRYPTION_LABEL)); enableEncryptionCheckBox = - CreateCheckBox(editPosX, rowPos - 2, editSizeX, ROW_HEIGHT, "", + CreateCheckBox(editPosX, rowPos - 2, editSizeX, ROW_HEIGHT, L"", ChildId::ENABLE_ENCRYPTION_CHECKBOX, enableEncryption); rowPos += INTERVAL + ROW_HEIGHT; - val = config.Get(FlightSqlConnection::TRUSTED_CERTS).c_str(); + val = config.Get(FlightSqlConnection::TRUSTED_CERTS); + std::wstring wVal = arrow::util::UTF8ToWideString(val).ValueOr(L""); labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, ROW_HEIGHT, - "Certificate:", ChildId::CERTIFICATE_LABEL)); + L"Certificate:", ChildId::CERTIFICATE_LABEL)); certificateEdit = CreateEdit(editPosX, rowPos, editSizeX - MARGIN - BUTTON_WIDTH, - ROW_HEIGHT, val, ChildId::CERTIFICATE_EDIT); + ROW_HEIGHT, wVal.c_str(), ChildId::CERTIFICATE_EDIT); certificateBrowseButton = CreateButton(editPosX + editSizeX - BUTTON_WIDTH, rowPos - 2, BUTTON_WIDTH, - BUTTON_HEIGHT, "Browse", ChildId::CERTIFICATE_BROWSE_BUTTON); + BUTTON_HEIGHT, L"Browse", ChildId::CERTIFICATE_BROWSE_BUTTON); rowPos += INTERVAL + ROW_HEIGHT; val = config.Get(FlightSqlConnection::USE_SYSTEM_TRUST_STORE).c_str(); const bool useSystemCertStore = driver::odbcabstraction::AsBool(val).value_or(true); - labels.push_back( - CreateLabel(labelPosX, rowPos, LABEL_WIDTH, 2 * ROW_HEIGHT, - "Use System Certificate Store:", ChildId::USE_SYSTEM_CERT_STORE_LABEL)); + labels.push_back(CreateLabel(labelPosX, rowPos, LABEL_WIDTH, 2 * ROW_HEIGHT, + L"Use System Certificate Store:", + ChildId::USE_SYSTEM_CERT_STORE_LABEL)); useSystemCertStoreCheckBox = - CreateCheckBox(editPosX, rowPos - 2, 20, 2 * ROW_HEIGHT, "", + CreateCheckBox(editPosX, rowPos - 2, 20, 2 * ROW_HEIGHT, L"", ChildId::USE_SYSTEM_CERT_STORE_CHECKBOX, useSystemCertStore); val = config.Get(FlightSqlConnection::DISABLE_CERTIFICATE_VERIFICATION).c_str(); @@ -273,17 +284,17 @@ int DsnConfigurationWindow::CreateEncryptionSettingsGroup(int posX, int posY, in const int rightCheckPosX = rightPosX + (editPosX - labelPosX); const bool disableCertVerification = driver::odbcabstraction::AsBool(val).value_or(false); - labels.push_back(CreateLabel( - rightPosX, rowPos, LABEL_WIDTH, 2 * ROW_HEIGHT, - "Disable Certificate Verification:", ChildId::DISABLE_CERT_VERIFICATION_LABEL)); + labels.push_back(CreateLabel(rightPosX, rowPos, LABEL_WIDTH, 2 * ROW_HEIGHT, + L"Disable Certificate Verification:", + ChildId::DISABLE_CERT_VERIFICATION_LABEL)); disableCertVerificationCheckBox = CreateCheckBox( - rightCheckPosX, rowPos - 2, 20, 2 * ROW_HEIGHT, "", + rightCheckPosX, rowPos - 2, 20, 2 * ROW_HEIGHT, L"", ChildId::DISABLE_CERT_VERIFICATION_CHECKBOX, disableCertVerification); - rowPos += INTERVAL + static_cast(1.5 * ROW_HEIGHT); + rowPos += INTERVAL + static_cast(1.5 * static_cast(ROW_HEIGHT)); encryptionSettingsGroupBox = - CreateGroupBox(posX, posY, sizeX, rowPos - posY, "Encryption settings", + CreateGroupBox(posX, posY, sizeX, rowPos - posY, L"Encryption settings", ChildId::AUTH_SETTINGS_GROUP_BOX); return rowPos - posY; @@ -301,12 +312,15 @@ int DsnConfigurationWindow::CreatePropertiesGroup(int posX, int posY, int sizeX) propertyList = CreateList(labelPosX, rowPos, listSize, listHeight, ChildId::PROPERTY_LIST); - propertyList->ListAddColumn("Key", 0, columnSize); - propertyList->ListAddColumn("Value", 1, columnSize); + propertyList->ListAddColumn(L"Key", 0, columnSize); + propertyList->ListAddColumn(L"Value", 1, columnSize); const auto keys = config.GetCustomKeys(); for (const auto& key : keys) { - propertyList->ListAddItem({std::string(key), config.Get(key)}); + std::wstring wKey = arrow::util::UTF8ToWideString(key).ValueOr(L""); + std::wstring wVal = arrow::util::UTF8ToWideString(config.Get(key)).ValueOr(L""); + + propertyList->ListAddItem({wKey, wVal}); } SendMessage(propertyList->GetHandle(), LVM_SETEXTENDEDLISTVIEWSTYLE, @@ -316,15 +330,15 @@ int DsnConfigurationWindow::CreatePropertiesGroup(int posX, int posY, int sizeX) int deletePosX = width - INTERVAL - MARGIN - BUTTON_WIDTH; int addPosX = deletePosX - INTERVAL - BUTTON_WIDTH; - addButton = CreateButton(addPosX, rowPos, BUTTON_WIDTH, BUTTON_HEIGHT, "Add", + addButton = CreateButton(addPosX, rowPos, BUTTON_WIDTH, BUTTON_HEIGHT, L"Add", ChildId::ADD_BUTTON); - deleteButton = CreateButton(deletePosX, rowPos, BUTTON_WIDTH, BUTTON_HEIGHT, "Delete", + deleteButton = CreateButton(deletePosX, rowPos, BUTTON_WIDTH, BUTTON_HEIGHT, L"Delete", ChildId::DELETE_BUTTON); rowPos += INTERVAL + BUTTON_HEIGHT; propertyGroupBox = CreateGroupBox(posX, posY, sizeX, rowPos - posY, - "Advanced properties", ChildId::PROPERTY_GROUP_BOX); + L"Advanced properties", ChildId::PROPERTY_GROUP_BOX); return rowPos - posY; } @@ -384,7 +398,7 @@ void DsnConfigurationWindow::CheckEnableOk() { void DsnConfigurationWindow::SaveParameters(Configuration& targetConfig) { targetConfig.Clear(); - std::string text; + std::wstring text; nameEdit->GetText(text); targetConfig.Set(FlightSqlConnection::DSN, text); serverEdit->GetText(text); @@ -427,7 +441,9 @@ void DsnConfigurationWindow::SaveParameters(Configuration& targetConfig) { // Get all the list properties. const auto properties = propertyList->ListGetAll(); for (const auto& property : properties) { - targetConfig.Set(property[0], property[1]); + std::string propertyKey = arrow::util::WideStringToUTF8(property[0]).ValueOr(""); + std::string propertyValue = arrow::util::WideStringToUTF8(property[1]).ValueOr(""); + targetConfig.Set(propertyKey, propertyValue); } } @@ -463,10 +479,13 @@ bool DsnConfigurationWindow::OnMessage(UINT msg, WPARAM wParam, LPARAM lParam) { SaveParameters(testConfig); std::string testMessage = TestConnection(testConfig); - MessageBox(NULL, testMessage.c_str(), "Test Connection Success", MB_OK); + std::wstring wTestMessage = + arrow::util::UTF8ToWideString(testMessage).ValueOr(L""); + MessageBox(NULL, wTestMessage.c_str(), L"Test Connection Success", MB_OK); } catch (odbcabstraction::DriverException& err) { - MessageBox(NULL, err.GetMessageText().c_str(), "Error!", - MB_ICONEXCLAMATION | MB_OK); + std::wstring wMessageText = + arrow::util::UTF8ToWideString(err.GetMessageText()).ValueOr(L""); + MessageBox(NULL, wMessageText.c_str(), L"Error!", MB_ICONEXCLAMATION | MB_OK); } break; @@ -477,8 +496,9 @@ bool DsnConfigurationWindow::OnMessage(UINT msg, WPARAM wParam, LPARAM lParam) { accepted = true; PostMessage(GetHandle(), WM_CLOSE, 0, 0); } catch (odbcabstraction::DriverException& err) { - MessageBox(NULL, err.GetMessageText().c_str(), "Error!", - MB_ICONEXCLAMATION | MB_OK); + std::wstring wMessageText = + arrow::util::UTF8ToWideString(err.GetMessageText()).ValueOr(L""); + MessageBox(NULL, wMessageText.c_str(), L"Error!", MB_ICONEXCLAMATION | MB_OK); } break; @@ -520,7 +540,7 @@ bool DsnConfigurationWindow::OnMessage(UINT msg, WPARAM wParam, LPARAM lParam) { case ChildId::CERTIFICATE_BROWSE_BUTTON: { OPENFILENAME openFileName; - char fileName[FILENAME_MAX]; + wchar_t fileName[FILENAME_MAX]; ZeroMemory(&openFileName, sizeof(openFileName)); openFileName.lStructSize = sizeof(openFileName); @@ -529,7 +549,7 @@ bool DsnConfigurationWindow::OnMessage(UINT msg, WPARAM wParam, LPARAM lParam) { openFileName.lpstrFile[0] = '\0'; openFileName.nMaxFile = FILENAME_MAX; // TODO: What type should this be? - openFileName.lpstrFilter = "All\0*.*"; + openFileName.lpstrFilter = L"All\0*.*"; openFileName.nFilterIndex = 1; openFileName.lpstrFileTitle = NULL; openFileName.nMaxFileTitle = 0; @@ -566,8 +586,8 @@ bool DsnConfigurationWindow::OnMessage(UINT msg, WPARAM wParam, LPARAM lParam) { addWindow.Update(); if (ProcessMessages(addWindow) == Result::OK) { - std::string key; - std::string value; + std::wstring key; + std::wstring value; addWindow.GetProperty(key, value); propertyList->ListAddItem({key, value}); } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/window.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/window.cc index f88cd8a3f880..2940c95578a8 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/window.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/ui/window.cc @@ -49,7 +49,7 @@ HINSTANCE GetHInstance() { return hInstance; } -Window::Window(Window* parent, const char* className, const char* title) +Window::Window(Window* parent, const wchar_t* className, const wchar_t* title) : className(className), title(title), handle(NULL), parent(parent), created(false) { // No-op. } @@ -88,7 +88,7 @@ void Window::Create(DWORD style, int posX, int posY, int width, int height, int } std::unique_ptr Window::CreateTabControl(int id) { - std::unique_ptr child(new Window(this, WC_TABCONTROL, "")); + std::unique_ptr child(new Window(this, WC_TABCONTROL, L"")); // Get the dimensions of the parent window's client area, and // create a tab control child window of that size. @@ -103,7 +103,7 @@ std::unique_ptr Window::CreateTabControl(int id) { std::unique_ptr Window::CreateList(int posX, int posY, int sizeX, int sizeY, int id) { - std::unique_ptr child(new Window(this, WC_LISTVIEW, "")); + std::unique_ptr child(new Window(this, WC_LISTVIEW, L"")); child->Create( WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT | LVS_EDITLABELS | WS_TABSTOP, posX, @@ -113,8 +113,8 @@ std::unique_ptr Window::CreateList(int posX, int posY, int sizeX, int si } std::unique_ptr Window::CreateGroupBox(int posX, int posY, int sizeX, int sizeY, - const char* title, int id) { - std::unique_ptr child(new Window(this, "Button", title)); + const wchar_t* title, int id) { + std::unique_ptr child(new Window(this, L"Button", title)); child->Create(WS_CHILD | WS_VISIBLE | BS_GROUPBOX, posX, posY, sizeX, sizeY, id); @@ -122,8 +122,8 @@ std::unique_ptr Window::CreateGroupBox(int posX, int posY, int sizeX, in } std::unique_ptr Window::CreateLabel(int posX, int posY, int sizeX, int sizeY, - const char* title, int id) { - std::unique_ptr child(new Window(this, "Static", title)); + const wchar_t* title, int id) { + std::unique_ptr child(new Window(this, L"Static", title)); child->Create(WS_CHILD | WS_VISIBLE, posX, posY, sizeX, sizeY, id); @@ -131,8 +131,8 @@ std::unique_ptr Window::CreateLabel(int posX, int posY, int sizeX, int s } std::unique_ptr Window::CreateEdit(int posX, int posY, int sizeX, int sizeY, - const char* title, int id, int style) { - std::unique_ptr child(new Window(this, "Edit", title)); + const wchar_t* title, int id, int style) { + std::unique_ptr child(new Window(this, L"Edit", title)); child->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL | WS_TABSTOP | style, posX, posY, sizeX, sizeY, id); @@ -141,8 +141,8 @@ std::unique_ptr Window::CreateEdit(int posX, int posY, int sizeX, int si } std::unique_ptr Window::CreateButton(int posX, int posY, int sizeX, int sizeY, - const char* title, int id, int style) { - std::unique_ptr child(new Window(this, "Button", title)); + const wchar_t* title, int id, int style) { + std::unique_ptr child(new Window(this, L"Button", title)); child->Create(WS_CHILD | WS_VISIBLE | WS_TABSTOP | style, posX, posY, sizeX, sizeY, id); @@ -150,8 +150,8 @@ std::unique_ptr Window::CreateButton(int posX, int posY, int sizeX, int } std::unique_ptr Window::CreateCheckBox(int posX, int posY, int sizeX, int sizeY, - const char* title, int id, bool state) { - std::unique_ptr child(new Window(this, "Button", title)); + const wchar_t* title, int id, bool state) { + std::unique_ptr child(new Window(this, L"Button", title)); child->Create(WS_CHILD | WS_VISIBLE | BS_CHECKBOX | WS_TABSTOP, posX, posY, sizeX, sizeY, id); @@ -162,8 +162,8 @@ std::unique_ptr Window::CreateCheckBox(int posX, int posY, int sizeX, in } std::unique_ptr Window::CreateComboBox(int posX, int posY, int sizeX, int sizeY, - const char* title, int id) { - std::unique_ptr child(new Window(this, "Combobox", title)); + const wchar_t* title, int id) { + std::unique_ptr child(new Window(this, L"Combobox", title)); child->Create(WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_TABSTOP, posX, posY, sizeX, sizeY, id); @@ -194,12 +194,12 @@ bool Window::IsTextEmpty() const { return (len <= 0); } -void Window::ListAddColumn(const std::string& name, int index, int width) { +void Window::ListAddColumn(const std::wstring& name, int index, int width) { LVCOLUMN lvc; lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT; lvc.cx = width; - lvc.pszText = const_cast(name.c_str()); + lvc.pszText = const_cast(name.c_str()); lvc.iSubItem = index; if (ListView_InsertColumn(handle, index, &lvc) == -1) { @@ -209,10 +209,10 @@ void Window::ListAddColumn(const std::string& name, int index, int width) { } } -void Window::ListAddItem(const std::vector& items) { +void Window::ListAddItem(const std::vector& items) { LVITEM lvi = {0}; lvi.mask = LVIF_TEXT; - lvi.pszText = const_cast(items[0].c_str()); + lvi.pszText = const_cast(items[0].c_str()); int ret = ListView_InsertItem(handle, &lvi); if (ret < 0) { @@ -223,7 +223,7 @@ void Window::ListAddItem(const std::vector& items) { for (size_t i = 1; i < items.size(); ++i) { ListView_SetItemText(handle, ret, static_cast(i), - const_cast(items[i].c_str())); + const_cast(items[i].c_str())); } } @@ -238,15 +238,15 @@ void Window::ListDeleteSelectedItem() { } } -std::vector > Window::ListGetAll() { +std::vector > Window::ListGetAll() { #define BUF_LEN 1024 - char buf[BUF_LEN]; + wchar_t buf[BUF_LEN]; - std::vector > values; + std::vector > values; const int numColumns = Header_GetItemCount(ListView_GetHeader(handle)); const int numItems = ListView_GetItemCount(handle); for (int i = 0; i < numItems; ++i) { - std::vector row; + std::vector row; for (int j = 0; j < numColumns; ++j) { ListView_GetItemText(handle, i, j, buf, BUF_LEN); row.emplace_back(buf); @@ -257,11 +257,11 @@ std::vector > Window::ListGetAll() { return values; } -void Window::AddTab(const std::string& name, int index) { +void Window::AddTab(const std::wstring& name, int index) { TCITEM tabControlItem; tabControlItem.mask = TCIF_TEXT | TCIF_IMAGE; tabControlItem.iImage = -1; - tabControlItem.pszText = const_cast(name.c_str()); + tabControlItem.pszText = const_cast(name.c_str()); if (TabCtrl_InsertItem(handle, index, &tabControlItem) == -1) { std::stringstream buf; buf << "Can not add tab, error code: " << GetLastError(); @@ -269,7 +269,7 @@ void Window::AddTab(const std::string& name, int index) { } } -void Window::GetText(std::string& text) const { +void Window::GetText(std::wstring& text) const { if (!IsEnabled()) { text.clear(); @@ -292,7 +292,7 @@ void Window::GetText(std::string& text) const { boost::algorithm::trim(text); } -void Window::SetText(const std::string& text) const { +void Window::SetText(const std::wstring& text) const { SNDMSG(handle, WM_SETTEXT, 0, reinterpret_cast(text.c_str())); } @@ -304,7 +304,7 @@ void Window::SetChecked(bool state) { Button_SetCheck(handle, state ? BST_CHECKED : BST_UNCHECKED); } -void Window::AddString(const std::string& str) { +void Window::AddString(const std::wstring& str) { SNDMSG(handle, CB_ADDSTRING, 0, reinterpret_cast(str.c_str())); } diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/utils.h b/cpp/src/arrow/flight/sql/odbc/flight_sql/utils.h index 586cfb22a305..8b3e14599a73 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/utils.h +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/utils.h @@ -17,13 +17,13 @@ #pragma once -#include -#include -#include #include #include #include #include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" +#include "arrow/flight/types.h" namespace driver { namespace flight_sql { diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/utils_test.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/utils_test.cc index 1575bf09fab1..f5d61da50bf4 100644 --- a/cpp/src/arrow/flight/sql/odbc/flight_sql/utils_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/utils_test.cc @@ -19,6 +19,7 @@ #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/calendar_utils.h" +#include "arrow/compute/initialize.h" #include "arrow/testing/builder.h" #include "arrow/testing/gtest_util.h" #include "arrow/testing/util.h" @@ -27,6 +28,13 @@ namespace driver { namespace flight_sql { +class UtilTestsWithCompute : public ::testing::Test { + public: + // This must be done before using the compute kernels in order to + // register them to the FunctionRegistry. + void SetUp() override { ASSERT_OK(arrow::compute::Initialize()); } +}; + void AssertConvertedArray(const std::shared_ptr& expected_array, const std::shared_ptr& converted_array, uint64_t size, arrow::Type::type arrow_type) { @@ -80,7 +88,7 @@ void TestTime64ArrayConversion(const std::vector& input, AssertConvertedArray(expected_array, converted_array, input.size(), arrow_type); } -TEST(Utils, Time32ToTimeStampArray) { +TEST_F(UtilTestsWithCompute, Time32ToTimeStampArray) { std::vector input_data = {14896, 17820}; const auto seconds_from_epoch = odbcabstraction::GetTodayTimeFromEpoch(); @@ -100,7 +108,7 @@ TEST(Utils, Time32ToTimeStampArray) { arrow::Type::TIMESTAMP); } -TEST(Utils, Time64ToTimeStampArray) { +TEST_F(UtilTestsWithCompute, Time64ToTimeStampArray) { std::vector input_data = {1579489200000, 1646881200000}; const auto seconds_from_epoch = odbcabstraction::GetTodayTimeFromEpoch(); @@ -120,7 +128,7 @@ TEST(Utils, Time64ToTimeStampArray) { arrow::Type::TIMESTAMP); } -TEST(Utils, StringToDateArray) { +TEST_F(UtilTestsWithCompute, StringToDateArray) { std::shared_ptr expected; arrow::ArrayFromVector({1579489200000, 1646881200000}, &expected); @@ -129,7 +137,7 @@ TEST(Utils, StringToDateArray) { odbcabstraction::CDataType_DATE, arrow::Type::DATE64); } -TEST(Utils, StringToTimeArray) { +TEST_F(UtilTestsWithCompute, StringToTimeArray) { std::shared_ptr expected; arrow::ArrayFromVector( time64(arrow::TimeUnit::MICRO), {36000000000, 43200000000}, &expected); diff --git a/cpp/src/arrow/flight/sql/odbc/flight_sql/win_system_dsn.cc b/cpp/src/arrow/flight/sql/odbc/flight_sql/win_system_dsn.cc new file mode 100644 index 000000000000..2017936dd904 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/flight_sql/win_system_dsn.cc @@ -0,0 +1,139 @@ +// 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. + +// platform.h includes windows.h, so it needs to be included +// before winuser.h +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" + +#include +#include + +#include "arrow/result.h" +#include "arrow/util/utf8.h" + +#include "arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h" +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h" +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/connection_string_parser.h" +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/dsn_configuration_window.h" +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/ui/window.h" +#include "arrow/flight/sql/odbc/flight_sql/system_dsn.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/logger.h" + +#include +#include +#include +#include + +using driver::flight_sql::FlightSqlConnection; +using driver::flight_sql::config::Configuration; +using driver::flight_sql::config::ConnectionStringParser; +using driver::flight_sql::config::DsnConfigurationWindow; +using driver::flight_sql::config::Result; +using driver::flight_sql::config::Window; +using driver::odbcabstraction::DriverException; + +bool DisplayConnectionWindow(void* windowParent, Configuration& config) { + HWND hwndParent = (HWND)windowParent; + + if (!hwndParent) return true; + + try { + Window parent(hwndParent); + DsnConfigurationWindow window(&parent, config); + + window.Create(); + + window.Show(); + window.Update(); + + return ProcessMessages(window) == Result::OK; + } catch (const DriverException& err) { + std::stringstream buf; + buf << "SQL State: " << err.GetSqlState() << ", Message: " << err.GetMessageText() + << ", Code: " << err.GetNativeError(); + std::wstring wMessage = arrow::util::UTF8ToWideString(buf.str()).ValueOr(L""); + MessageBox(NULL, wMessage.c_str(), L"Error!", MB_ICONEXCLAMATION | MB_OK); + + std::wstring wMessageText = + arrow::util::UTF8ToWideString(err.GetMessageText()).ValueOr(L""); + SQLPostInstallerError(err.GetNativeError(), wMessageText.c_str()); + } + + return false; +} + +bool DisplayConnectionWindow(void* windowParent, Configuration& config, + Connection::ConnPropertyMap& properties) { + for (const auto& [key, value] : properties) { + config.Set(key, value); + } + + if (DisplayConnectionWindow(windowParent, config)) { + properties = config.GetProperties(); + return true; + } else { + LOG_INFO("Dialog is cancelled by user"); + return false; + } +} + +BOOL INSTAPI ConfigDSNW(HWND hwndParent, WORD req, LPCWSTR wDriver, LPCWSTR wAttributes) { + Configuration config; + ConnectionStringParser parser(config); + std::string attributes = + arrow::util::WideStringToUTF8(std::wstring(wAttributes)).ValueOr(""); + parser.ParseConfigAttributes(attributes.c_str()); + + switch (req) { + case ODBC_ADD_DSN: { + config.LoadDefaults(); + if (!DisplayConnectionWindow(hwndParent, config) || !RegisterDsn(config, wDriver)) + return FALSE; + + break; + } + + case ODBC_CONFIG_DSN: { + const std::string& dsn = config.Get(FlightSqlConnection::DSN); + std::wstring wDsn = arrow::util::UTF8ToWideString(dsn).ValueOr(L""); + if (!SQLValidDSN(wDsn.c_str())) return FALSE; + + Configuration loaded(config); + loaded.LoadDsn(dsn); + + if (!DisplayConnectionWindow(hwndParent, loaded) || !UnregisterDsn(wDsn.c_str()) || + !RegisterDsn(loaded, wDriver)) + return FALSE; + + break; + } + + case ODBC_REMOVE_DSN: { + const std::string& dsn = config.Get(FlightSqlConnection::DSN); + std::wstring wDsn = arrow::util::UTF8ToWideString(dsn).ValueOr(L""); + if (!SQLValidDSN(wDsn.c_str()) || !UnregisterDsn(wDsn)) return FALSE; + + break; + } + + default: + return FALSE; + } + + return TRUE; +} diff --git a/cpp/src/arrow/flight/sql/odbc/install/install_amd64.cmd b/cpp/src/arrow/flight/sql/odbc/install/install_amd64.cmd new file mode 100644 index 000000000000..b1fd85d578ea --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/install_amd64.cmd @@ -0,0 +1,53 @@ +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. + +@echo off + +set ODBC_AMD64=%1 + +@REM enable delayed variable expansion to make environment variables enclosed with "!" to be evaluated +@REM when the command is executed instead of when the command is parsed +setlocal enableextensions enabledelayedexpansion + +if [%ODBC_AMD64%] == [] ( + echo error: 64-bit driver is not specified. Call format: install_amd64 abs_path_to_64_bit_driver + pause + exit /b 1 +) + +if exist %ODBC_AMD64% ( + for %%i IN (%ODBC_AMD64%) DO IF EXIST %%~si\NUL ( + echo warning: The path you have specified seems to be a directory. Note that you have to specify path to driver file itself instead. + ) + echo Installing 64-bit driver: %ODBC_AMD64% + reg add "HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\Apache Arrow Flight SQL ODBC Driver" /v DriverODBCVer /t REG_SZ /d "03.80" /f + reg add "HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\Apache Arrow Flight SQL ODBC Driver" /v UsageCount /t REG_DWORD /d 00000001 /f + reg add "HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\Apache Arrow Flight SQL ODBC Driver" /v Driver /t REG_SZ /d %ODBC_AMD64% /f + reg add "HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\Apache Arrow Flight SQL ODBC Driver" /v Setup /t REG_SZ /d %ODBC_AMD64% /f + reg add "HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers" /v "Apache Arrow Flight SQL ODBC Driver" /t REG_SZ /d "Installed" /f + + IF !ERRORLEVEL! NEQ 0 ( + echo Error occurred while registering 64-bit driver. Exiting. + echo ERRORLEVEL: !ERRORLEVEL! + exit !ERRORLEVEL! + ) +) else ( + echo 64-bit driver can not be found: %ODBC_AMD64% + echo Call format: install_amd64 abs_path_to_64_bit_driver + pause + exit /b 1 +) diff --git a/cpp/src/arrow/flight/sql/odbc/odbc.def b/cpp/src/arrow/flight/sql/odbc/odbc.def new file mode 100644 index 000000000000..8ba5b3fff788 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/odbc.def @@ -0,0 +1,61 @@ +; 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. + +LIBRARY arrow_flight_sql_odbc +EXPORTS + ConfigDSNW + SQLAllocConnect + SQLAllocEnv + SQLAllocHandle + SQLAllocStmt + SQLBindCol + SQLCancel + SQLCloseCursor + SQLColAttributeW + SQLColumnsW + SQLConnectW + SQLDescribeColW + SQLDisconnect + SQLDriverConnectW + SQLExecDirectW + SQLExecute + SQLExtendedFetch + SQLFetch + SQLFetchScroll + SQLForeignKeysW + SQLFreeEnv + SQLFreeConnect + SQLFreeHandle + SQLFreeStmt + SQLGetConnectAttrW + SQLGetData + SQLGetDiagFieldW + SQLGetDiagRecW + SQLGetEnvAttr + SQLGetInfoW + SQLGetStmtAttrW + SQLGetTypeInfoW + SQLRowCount + SQLMoreResults + SQLNativeSqlW + SQLNumResultCols + SQLPrepareW + SQLPrimaryKeysW + SQLSetConnectAttrW + SQLSetEnvAttr + SQLSetStmtAttrW + SQLTablesW diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc new file mode 100644 index 000000000000..f76bda7285e4 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -0,0 +1,1422 @@ +// 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. + +// flight_sql_connection.h needs to be included first due to conflicts with windows.h +#include "arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h" + +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h" +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/flight_sql_driver.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/logger.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/encoding_utils.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_environment.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h" + +#if defined _WIN32 || defined _WIN64 +// For displaying DSN Window +# include "arrow/flight/sql/odbc/flight_sql/system_dsn.h" +#endif + +// odbc_api includes windows.h, which needs to be put behind winsock2.h. +// odbc_environment.h includes winsock2.h +#include "arrow/flight/sql/odbc/odbc_api.h" + +namespace arrow { +SQLRETURN SQLAllocHandle(SQLSMALLINT type, SQLHANDLE parent, SQLHANDLE* result) { + LOG_DEBUG("SQLAllocHandle called with type: {}, parent: {}, result: {}", type, parent, + fmt::ptr(result)); + + *result = nullptr; + + switch (type) { + case SQL_HANDLE_ENV: { + using driver::flight_sql::FlightSqlDriver; + using ODBC::ODBCEnvironment; + + *result = SQL_NULL_HENV; + + try { + static std::shared_ptr odbc_driver = + std::make_shared(); + *result = reinterpret_cast(new ODBCEnvironment(odbc_driver)); + + return SQL_SUCCESS; + } catch (const std::bad_alloc&) { + // allocating environment failed so cannot log diagnostic error here + return SQL_ERROR; + } + } + + case SQL_HANDLE_DBC: { + using ODBC::ODBCConnection; + using ODBC::ODBCEnvironment; + + *result = SQL_NULL_HDBC; + + ODBCEnvironment* environment = reinterpret_cast(parent); + + return ODBCEnvironment::ExecuteWithDiagnostics(environment, SQL_ERROR, [=]() { + std::shared_ptr conn = environment->CreateConnection(); + + if (conn) { + *result = reinterpret_cast(conn.get()); + + return SQL_SUCCESS; + } + + return SQL_ERROR; + }); + } + + case SQL_HANDLE_STMT: { + using ODBC::ODBCConnection; + using ODBC::ODBCStatement; + + *result = SQL_NULL_HSTMT; + + ODBCConnection* connection = reinterpret_cast(parent); + + return ODBCConnection::ExecuteWithDiagnostics(connection, SQL_ERROR, [=]() { + std::shared_ptr statement = connection->createStatement(); + + if (statement) { + *result = reinterpret_cast(statement.get()); + + return SQL_SUCCESS; + } + + return SQL_ERROR; + }); + } + + // TODO Implement for case of descriptor + case SQL_HANDLE_DESC: + return SQL_INVALID_HANDLE; + + default: + break; + } + + return SQL_ERROR; +} + +SQLRETURN SQLFreeHandle(SQLSMALLINT type, SQLHANDLE handle) { + LOG_DEBUG("SQLFreeHandle called with type: {}, handle: {}", type, handle); + + switch (type) { + case SQL_HANDLE_ENV: { + using ODBC::ODBCEnvironment; + + ODBCEnvironment* environment = reinterpret_cast(handle); + + if (!environment) { + return SQL_INVALID_HANDLE; + } + + delete environment; + + return SQL_SUCCESS; + } + + case SQL_HANDLE_DBC: { + using ODBC::ODBCConnection; + + ODBCConnection* conn = reinterpret_cast(handle); + + if (!conn) { + return SQL_INVALID_HANDLE; + } + + conn->releaseConnection(); + + return SQL_SUCCESS; + } + + case SQL_HANDLE_STMT: { + using ODBC::ODBCStatement; + + ODBCStatement* statement = reinterpret_cast(handle); + + if (!statement) { + return SQL_INVALID_HANDLE; + } + + statement->releaseStatement(); + + return SQL_SUCCESS; + } + + case SQL_HANDLE_DESC: + return SQL_INVALID_HANDLE; + + default: + break; + } + + return SQL_ERROR; +} + +SQLRETURN SQLFreeStmt(SQLHSTMT handle, SQLUSMALLINT option) { + switch (option) { + case SQL_CLOSE: { + using ODBC::ODBCStatement; + + return ODBCStatement::ExecuteWithDiagnostics(handle, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(handle); + + // Close cursor with suppressErrors set to true + statement->closeCursor(true); + + return SQL_SUCCESS; + }); + } + + case SQL_DROP: { + return SQLFreeHandle(SQL_HANDLE_STMT, handle); + } + + case SQL_UNBIND: { + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(handle, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(handle); + ODBCDescriptor* ard = statement->GetARD(); + // Unbind columns + ard->SetHeaderField(SQL_DESC_COUNT, (void*)0, 0); + return SQL_SUCCESS; + }); + } + + // SQLBindParameter is not supported + case SQL_RESET_PARAMS: { + return SQL_SUCCESS; + } + } + + return SQL_ERROR; +} + +inline bool IsValidStringFieldArgs(SQLPOINTER diagInfoPtr, SQLSMALLINT bufferLength, + SQLSMALLINT* stringLengthPtr, bool isUnicode) { + const SQLSMALLINT charSize = isUnicode ? GetSqlWCharSize() : sizeof(char); + const bool hasValidBuffer = + diagInfoPtr && bufferLength >= 0 && bufferLength % charSize == 0; + + // regardless of capacity return false if invalid + if (diagInfoPtr && !hasValidBuffer) { + return false; + } + + return hasValidBuffer || stringLengthPtr; +} + +SQLRETURN SQLGetDiagField(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLINT recNumber, + SQLSMALLINT diagIdentifier, SQLPOINTER diagInfoPtr, + SQLSMALLINT bufferLength, SQLSMALLINT* stringLengthPtr) { + // TODO: Implement additional fields types + // https://github.com/apache/arrow/issues/46573 + LOG_DEBUG( + "SQLGetDiagFieldW called with handleType: {}, handle: {}, recNumber: {}, " + "diagIdentifier: {}, diagInfoPtr: {}, bufferLength: {}, stringLengthPtr: {}", + handleType, handle, recNumber, diagIdentifier, diagInfoPtr, bufferLength, + fmt::ptr(stringLengthPtr)); + + using driver::odbcabstraction::Diagnostics; + using ODBC::GetStringAttribute; + using ODBC::ODBCConnection; + using ODBC::ODBCEnvironment; + using ODBC::ODBCStatement; + + if (!handle) { + return SQL_INVALID_HANDLE; + } + + if (!diagInfoPtr && !stringLengthPtr) { + return SQL_ERROR; + } + + // If buffer length derived from null terminated string + if (diagInfoPtr && bufferLength == SQL_NTS) { + const wchar_t* str = reinterpret_cast(diagInfoPtr); + bufferLength = wcslen(str) * driver::odbcabstraction::GetSqlWCharSize(); + } + + // Set character type to be Unicode by default + const bool isUnicode = true; + Diagnostics* diagnostics = nullptr; + + switch (handleType) { + case SQL_HANDLE_ENV: { + ODBCEnvironment* environment = reinterpret_cast(handle); + diagnostics = &environment->GetDiagnostics(); + break; + } + + case SQL_HANDLE_DBC: { + ODBCConnection* connection = reinterpret_cast(handle); + diagnostics = &connection->GetDiagnostics(); + break; + } + + case SQL_HANDLE_DESC: { + return SQL_ERROR; + } + + case SQL_HANDLE_STMT: { + ODBCStatement* statement = reinterpret_cast(handle); + diagnostics = &statement->GetDiagnostics(); + break; + } + + default: + return SQL_ERROR; + } + + if (!diagnostics) { + return SQL_ERROR; + } + + // Retrieve and return if header level diagnostics + switch (diagIdentifier) { + case SQL_DIAG_NUMBER: { + if (diagInfoPtr) { + *static_cast(diagInfoPtr) = + static_cast(diagnostics->GetRecordCount()); + } + + if (stringLengthPtr) { + *stringLengthPtr = sizeof(SQLINTEGER); + } + + return SQL_SUCCESS; + } + + // TODO implement return code function + case SQL_DIAG_RETURNCODE: { + return SQL_SUCCESS; + } + + case SQL_DIAG_CURSOR_ROW_COUNT: { + if (handleType == SQL_HANDLE_STMT) { + if (diagInfoPtr) { + // Will always be 0 if only SELECT supported + *static_cast(diagInfoPtr) = 0; + } + + if (stringLengthPtr) { + *stringLengthPtr = sizeof(SQLLEN); + } + + return SQL_SUCCESS; + } + + return SQL_ERROR; + } + + // Not supported + case SQL_DIAG_DYNAMIC_FUNCTION: + case SQL_DIAG_DYNAMIC_FUNCTION_CODE: { + if (handleType == SQL_HANDLE_STMT) { + return SQL_SUCCESS; + } + + return SQL_ERROR; + } + + case SQL_DIAG_ROW_COUNT: { + if (handleType == SQL_HANDLE_STMT) { + if (diagInfoPtr) { + // Will always be 0 if only SELECT is supported + *static_cast(diagInfoPtr) = 0; + } + + if (stringLengthPtr) { + *stringLengthPtr = sizeof(SQLLEN); + } + + return SQL_SUCCESS; + } + + return SQL_ERROR; + } + } + + // If not a diagnostic header field then the record number must be 1 or greater + if (recNumber < 1) { + return SQL_ERROR; + } + + // Retrieve record level diagnostics from specified 1 based record + const uint32_t recordIndex = static_cast(recNumber - 1); + if (!diagnostics->HasRecord(recordIndex)) { + return SQL_NO_DATA; + } + + // Retrieve record field data + switch (diagIdentifier) { + case SQL_DIAG_MESSAGE_TEXT: { + if (IsValidStringFieldArgs(diagInfoPtr, bufferLength, stringLengthPtr, isUnicode)) { + const std::string& message = diagnostics->GetMessageText(recordIndex); + return GetStringAttribute(isUnicode, message, true, diagInfoPtr, bufferLength, + stringLengthPtr, *diagnostics); + } + + return SQL_ERROR; + } + + case SQL_DIAG_NATIVE: { + if (diagInfoPtr) { + *static_cast(diagInfoPtr) = diagnostics->GetNativeError(recordIndex); + } + + if (stringLengthPtr) { + *stringLengthPtr = sizeof(SQLINTEGER); + } + + return SQL_SUCCESS; + } + + case SQL_DIAG_SERVER_NAME: { + if (IsValidStringFieldArgs(diagInfoPtr, bufferLength, stringLengthPtr, isUnicode)) { + switch (handleType) { + case SQL_HANDLE_DBC: { + ODBCConnection* connection = reinterpret_cast(handle); + std::string dsn = connection->GetDSN(); + return GetStringAttribute(isUnicode, dsn, true, diagInfoPtr, bufferLength, + stringLengthPtr, *diagnostics); + } + + case SQL_HANDLE_DESC: { + // TODO Implement for case of descriptor + return SQL_ERROR; + } + + case SQL_HANDLE_STMT: { + ODBCStatement* statement = reinterpret_cast(handle); + ODBCConnection* connection = &statement->GetConnection(); + std::string dsn = connection->GetDSN(); + return GetStringAttribute(isUnicode, dsn, true, diagInfoPtr, bufferLength, + stringLengthPtr, *diagnostics); + } + + default: + return SQL_ERROR; + } + } + + return SQL_ERROR; + } + + case SQL_DIAG_SQLSTATE: { + if (IsValidStringFieldArgs(diagInfoPtr, bufferLength, stringLengthPtr, isUnicode)) { + const std::string& state = diagnostics->GetSQLState(recordIndex); + return GetStringAttribute(isUnicode, state, true, diagInfoPtr, bufferLength, + stringLengthPtr, *diagnostics); + } + + return SQL_ERROR; + } + + // Return valid dummy variable for unimplemented field + case SQL_DIAG_COLUMN_NUMBER: { + if (diagInfoPtr) { + *static_cast(diagInfoPtr) = SQL_NO_COLUMN_NUMBER; + } + + if (stringLengthPtr) { + *stringLengthPtr = sizeof(SQLINTEGER); + } + + return SQL_SUCCESS; + } + + // Return empty string dummy variable for unimplemented fields + case SQL_DIAG_CLASS_ORIGIN: + case SQL_DIAG_CONNECTION_NAME: + case SQL_DIAG_SUBCLASS_ORIGIN: { + if (IsValidStringFieldArgs(diagInfoPtr, bufferLength, stringLengthPtr, isUnicode)) { + return GetStringAttribute(isUnicode, "", true, diagInfoPtr, bufferLength, + stringLengthPtr, *diagnostics); + } + + return SQL_ERROR; + } + + // Return valid dummy variable for unimplemented field + case SQL_DIAG_ROW_NUMBER: { + if (diagInfoPtr) { + *static_cast(diagInfoPtr) = SQL_NO_ROW_NUMBER; + } + + if (stringLengthPtr) { + *stringLengthPtr = sizeof(SQLLEN); + } + + return SQL_SUCCESS; + } + + default: { + return SQL_ERROR; + } + } + + return SQL_ERROR; +} + +SQLRETURN SQLGetDiagRec(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLINT recNumber, + SQLWCHAR* sqlState, SQLINTEGER* nativeErrorPtr, + SQLWCHAR* messageText, SQLSMALLINT bufferLength, + SQLSMALLINT* textLengthPtr) { + LOG_DEBUG( + "SQLGetDiagRecW called with handleType: {}, handle: {}, recNumber: {}, " + "sqlState: {}, nativeErrorPtr: {}, messageText: {}, bufferLength: {}, " + "textLengthPtr: {}", + handleType, handle, recNumber, fmt::ptr(sqlState), fmt::ptr(nativeErrorPtr), + fmt::ptr(messageText), bufferLength, fmt::ptr(textLengthPtr)); + + using driver::odbcabstraction::Diagnostics; + using ODBC::GetStringAttribute; + using ODBC::ODBCConnection; + using ODBC::ODBCEnvironment; + using ODBC::ODBCStatement; + + if (!handle) { + return SQL_INVALID_HANDLE; + } + + // Record number must be greater or equal to 1 + if (recNumber < 1 || bufferLength < 0) { + return SQL_ERROR; + } + + // Set character type to be Unicode by default + const bool isUnicode = true; + Diagnostics* diagnostics = nullptr; + + switch (handleType) { + case SQL_HANDLE_ENV: { + auto* environment = ODBCEnvironment::of(handle); + diagnostics = &environment->GetDiagnostics(); + break; + } + + case SQL_HANDLE_DBC: { + auto* connection = ODBCConnection::of(handle); + diagnostics = &connection->GetDiagnostics(); + break; + } + + case SQL_HANDLE_DESC: { + return SQL_ERROR; + } + + case SQL_HANDLE_STMT: { + auto* statement = ODBCStatement::of(handle); + diagnostics = &statement->GetDiagnostics(); + break; + } + + default: + return SQL_INVALID_HANDLE; + } + + if (!diagnostics) { + return SQL_ERROR; + } + + // Convert from ODBC 1 based record number to internal diagnostics 0 indexed storage + const size_t recordIndex = static_cast(recNumber - 1); + if (!diagnostics->HasRecord(recordIndex)) { + return SQL_NO_DATA; + } + + if (sqlState) { + // The length of the sql state is always 5 characters plus null + SQLSMALLINT size = 6; + const std::string& state = diagnostics->GetSQLState(recordIndex); + GetStringAttribute(isUnicode, state, false, sqlState, size, &size, *diagnostics); + } + + if (nativeErrorPtr) { + *nativeErrorPtr = diagnostics->GetNativeError(recordIndex); + } + + if (messageText || textLengthPtr) { + const std::string& message = diagnostics->GetMessageText(recordIndex); + return GetStringAttribute(isUnicode, message, false, messageText, bufferLength, + textLengthPtr, *diagnostics); + } + + return SQL_SUCCESS; +} + +SQLRETURN SQLGetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER valuePtr, + SQLINTEGER bufferLength, SQLINTEGER* strLenPtr) { + LOG_DEBUG( + "SQLGetEnvAttr called with env: {}, attr: {}, valuePtr: {}, " + "bufferLength: {}, strLenPtr: {}", + env, attr, valuePtr, bufferLength, fmt::ptr(strLenPtr)); + + using driver::odbcabstraction::DriverException; + using ODBC::ODBCEnvironment; + + ODBCEnvironment* environment = reinterpret_cast(env); + + return ODBCEnvironment::ExecuteWithDiagnostics(environment, SQL_ERROR, [=]() { + switch (attr) { + case SQL_ATTR_ODBC_VERSION: { + if (!valuePtr && !strLenPtr) { + throw DriverException("Invalid null pointer for attribute.", "HY000"); + } + + if (valuePtr) { + SQLINTEGER* value = reinterpret_cast(valuePtr); + *value = static_cast(environment->getODBCVersion()); + } + + if (strLenPtr) { + *strLenPtr = sizeof(SQLINTEGER); + } + + return SQL_SUCCESS; + } + + case SQL_ATTR_OUTPUT_NTS: { + if (!valuePtr && !strLenPtr) { + throw DriverException("Invalid null pointer for attribute.", "HY000"); + } + + if (valuePtr) { + // output nts always returns SQL_TRUE + SQLINTEGER* value = reinterpret_cast(valuePtr); + *value = SQL_TRUE; + } + + if (strLenPtr) { + *strLenPtr = sizeof(SQLINTEGER); + } + + return SQL_SUCCESS; + } + + case SQL_ATTR_CONNECTION_POOLING: + case SQL_ATTR_APP_ROW_DESC: { + throw DriverException("Optional feature not supported.", "HYC00"); + } + + default: { + throw DriverException("Invalid attribute", "HYC00"); + } + } + }); +} + +SQLRETURN SQLSetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER valuePtr, + SQLINTEGER strLen) { + LOG_DEBUG( + "SQLSetEnvAttr called with env: {}, attr: {}, valuePtr: {}, " + "strLen: {}", + env, attr, valuePtr, strLen); + + using driver::odbcabstraction::DriverException; + using ODBC::ODBCEnvironment; + + ODBCEnvironment* environment = reinterpret_cast(env); + + return ODBCEnvironment::ExecuteWithDiagnostics(environment, SQL_ERROR, [=]() { + if (!valuePtr) { + throw DriverException("Invalid null pointer for attribute.", "HY024"); + } + + switch (attr) { + case SQL_ATTR_ODBC_VERSION: { + SQLINTEGER version = + static_cast(reinterpret_cast(valuePtr)); + if (version == SQL_OV_ODBC2 || version == SQL_OV_ODBC3) { + environment->setODBCVersion(version); + + return SQL_SUCCESS; + } else { + throw DriverException("Invalid value for attribute", "HY024"); + } + } + + case SQL_ATTR_OUTPUT_NTS: { + // output nts can not be set to SQL_FALSE, is always SQL_TRUE + SQLINTEGER value = static_cast(reinterpret_cast(valuePtr)); + if (value == SQL_TRUE) { + return SQL_SUCCESS; + } else { + throw DriverException("Invalid value for attribute", "HY024"); + } + } + + case SQL_ATTR_CONNECTION_POOLING: + case SQL_ATTR_APP_ROW_DESC: { + throw DriverException("Optional feature not supported.", "HYC00"); + } + + default: { + throw DriverException("Invalid attribute", "HY092"); + } + } + }); +} + +SQLRETURN SQLGetConnectAttr(SQLHDBC conn, SQLINTEGER attribute, SQLPOINTER valuePtr, + SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr) { + LOG_DEBUG( + "SQLGetConnectAttrW called with conn: {}, attribute: {}, valuePtr: {}, " + "bufferLength: {}, stringLengthPtr: {}", + conn, attribute, valuePtr, bufferLength, fmt::ptr(stringLengthPtr)); + + using driver::odbcabstraction::Connection; + using ODBC::ODBCConnection; + + return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() { + const bool isUnicode = true; + ODBCConnection* connection = reinterpret_cast(conn); + return connection->GetConnectAttr(attribute, valuePtr, bufferLength, stringLengthPtr, + isUnicode); + }); +} + +SQLRETURN SQLSetConnectAttr(SQLHDBC conn, SQLINTEGER attr, SQLPOINTER valuePtr, + SQLINTEGER valueLen) { + LOG_DEBUG( + "SQLSetConnectAttrW called with conn: {}, attr: {}, valuePtr: {}, valueLen: {}", + conn, attr, valuePtr, valueLen); + + using driver::odbcabstraction::Connection; + using ODBC::ODBCConnection; + + return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() { + const bool isUnicode = true; + ODBCConnection* connection = reinterpret_cast(conn); + connection->SetConnectAttr(attr, valuePtr, valueLen, isUnicode); + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLDriverConnect(SQLHDBC conn, SQLHWND windowHandle, + SQLWCHAR* inConnectionString, + SQLSMALLINT inConnectionStringLen, + SQLWCHAR* outConnectionString, + SQLSMALLINT outConnectionStringBufferLen, + SQLSMALLINT* outConnectionStringLen, + SQLUSMALLINT driverCompletion) { + LOG_DEBUG( + "SQLDriverConnectW called with conn: {}, windowHandle: {}, inConnectionString: {}, " + "inConnectionStringLen: {}, outConnectionString: {}, outConnectionStringBufferLen: " + "{}, outConnectionStringLen: {}, driverCompletion: {}", + conn, fmt::ptr(windowHandle), fmt::ptr(inConnectionString), inConnectionStringLen, + fmt::ptr(outConnectionString), outConnectionStringBufferLen, + fmt::ptr(outConnectionStringLen), driverCompletion); + + // TODO: Implement FILEDSN and SAVEFILE keywords according to the spec + // https://github.com/apache/arrow/issues/46449 + + // TODO: Copy connection string properly in SQLDriverConnect according to the + // spec https://github.com/apache/arrow/issues/46560 + + using driver::odbcabstraction::Connection; + using driver::odbcabstraction::DriverException; + using ODBC::ODBCConnection; + + return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() { + ODBCConnection* connection = reinterpret_cast(conn); + std::string connection_string = + ODBC::SqlWcharToString(inConnectionString, inConnectionStringLen); + Connection::ConnPropertyMap properties; + std::string dsn = + ODBCConnection::getPropertiesFromConnString(connection_string, properties); + + std::vector missing_properties; + + // TODO: Implement SQL_DRIVER_COMPLETE_REQUIRED in SQLDriverConnect according to the + // spec https://github.com/apache/arrow/issues/46448 +#if defined _WIN32 || defined _WIN64 + // Load the DSN window according to driverCompletion + if (driverCompletion == SQL_DRIVER_PROMPT) { + // Load DSN window before first attempt to connect + driver::flight_sql::config::Configuration config; + if (!DisplayConnectionWindow(windowHandle, config, properties)) { + return static_cast(SQL_NO_DATA); + } + connection->connect(dsn, properties, missing_properties); + } else if (driverCompletion == SQL_DRIVER_COMPLETE || + driverCompletion == SQL_DRIVER_COMPLETE_REQUIRED) { + try { + connection->connect(dsn, properties, missing_properties); + } catch (const DriverException&) { + // If first connection fails due to missing attributes, load + // the DSN window and try to connect again + if (!missing_properties.empty()) { + driver::flight_sql::config::Configuration config; + missing_properties.clear(); + + if (!DisplayConnectionWindow(windowHandle, config, properties)) { + return static_cast(SQL_NO_DATA); + } + connection->connect(dsn, properties, missing_properties); + } else { + throw; + } + } + } else { + // Default case: attempt connection without showing DSN window + connection->connect(dsn, properties, missing_properties); + } +#else + // Attempt connection without loading DSN window on macOS/Linux + connection->connect(dsn, properties, missing_properties); +#endif + // Copy connection string to outConnectionString after connection attempt + return ODBC::GetStringAttribute(true, connection_string, false, outConnectionString, + outConnectionStringBufferLen, outConnectionStringLen, + connection->GetDiagnostics()); + }); +} + +SQLRETURN SQLConnect(SQLHDBC conn, SQLWCHAR* dsnName, SQLSMALLINT dsnNameLen, + SQLWCHAR* userName, SQLSMALLINT userNameLen, SQLWCHAR* password, + SQLSMALLINT passwordLen) { + LOG_DEBUG( + "SQLConnectW called with conn: {}, dsnName: {}, dsnNameLen: {}, userName: {}, " + "userNameLen: {}, password: {}, passwordLen: {}", + conn, fmt::ptr(dsnName), dsnNameLen, fmt::ptr(userName), userNameLen, + fmt::ptr(password), passwordLen); + + using driver::flight_sql::FlightSqlConnection; + using driver::flight_sql::config::Configuration; + using ODBC::ODBCConnection; + + using ODBC::SqlWcharToString; + + return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() { + ODBCConnection* connection = reinterpret_cast(conn); + std::string dsn = SqlWcharToString(dsnName, dsnNameLen); + + Configuration config; + config.LoadDsn(dsn); + + if (userName) { + std::string uid = SqlWcharToString(userName, userNameLen); + config.Emplace(FlightSqlConnection::UID, std::move(uid)); + } + + if (password) { + std::string pwd = SqlWcharToString(password, passwordLen); + config.Emplace(FlightSqlConnection::PWD, std::move(pwd)); + } + + std::vector missing_properties; + + connection->connect(dsn, config.GetProperties(), missing_properties); + + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLDisconnect(SQLHDBC conn) { + LOG_DEBUG("SQLDisconnect called with conn: {}", conn); + + using ODBC::ODBCConnection; + + return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() { + ODBCConnection* connection = reinterpret_cast(conn); + + connection->disconnect(); + + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLGetInfo(SQLHDBC conn, SQLUSMALLINT infoType, SQLPOINTER infoValuePtr, + SQLSMALLINT bufLen, SQLSMALLINT* stringLengthPtr) { + LOG_DEBUG( + "SQLGetInfo called with conn: {}, infoType: {}, infoValuePtr: {}, bufLen: {}, " + "stringLengthPtr: {}", + conn, infoType, infoValuePtr, bufLen, fmt::ptr(stringLengthPtr)); + + using ODBC::ODBCConnection; + + return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() { + ODBCConnection* connection = reinterpret_cast(conn); + + // Set character type to be Unicode by default + const bool isUnicode = true; + + if (!infoValuePtr && !stringLengthPtr) { + return static_cast SQL_ERROR; + } + + return connection->GetInfo(infoType, infoValuePtr, bufLen, stringLengthPtr, + isUnicode); + }); +} + +SQLRETURN SQLGetStmtAttr(SQLHSTMT stmt, SQLINTEGER attribute, SQLPOINTER valuePtr, + SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr) { + LOG_DEBUG( + "SQLGetStmtAttrW called with stmt: {}, attribute: {}, valuePtr: {}, " + "bufferLength: {}, stringLengthPtr: {}", + stmt, attribute, valuePtr, bufferLength, fmt::ptr(stringLengthPtr)); + using ODBC::ODBCStatement; + + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + + bool isUnicode = true; + + statement->GetStmtAttr(attribute, valuePtr, bufferLength, stringLengthPtr, isUnicode); + + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLSetStmtAttr(SQLHSTMT stmt, SQLINTEGER attribute, SQLPOINTER valuePtr, + SQLINTEGER stringLength) { + LOG_DEBUG( + "SQLSetStmtAttrW called with stmt: {}, attribute: {}, valuePtr: {}, " + "stringLength: {}", + stmt, attribute, valuePtr, stringLength); + using ODBC::ODBCStatement; + + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + + bool isUnicode = true; + + statement->SetStmtAttr(attribute, valuePtr, stringLength, isUnicode); + + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLExecDirect(SQLHSTMT stmt, SQLWCHAR* queryText, SQLINTEGER textLength) { + LOG_DEBUG("SQLExecDirectW called with stmt: {}, queryText: {}, textLength: {}", stmt, + fmt::ptr(queryText), textLength); + using ODBC::ODBCStatement; + // The driver is built to handle SELECT statements only. + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + std::string query = ODBC::SqlWcharToString(queryText, textLength); + + statement->Prepare(query); + statement->ExecutePrepared(); + + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLPrepare(SQLHSTMT stmt, SQLWCHAR* queryText, SQLINTEGER textLength) { + LOG_DEBUG("SQLPrepareW called with stmt: {}, queryText: {}, textLength: {}", stmt, + fmt::ptr(queryText), textLength); + using ODBC::ODBCStatement; + // The driver is built to handle SELECT statements only. + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + std::string query = ODBC::SqlWcharToString(queryText, textLength); + + statement->Prepare(query); + + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLExecute(SQLHSTMT stmt) { + LOG_DEBUG("SQLExecute called with stmt: {}", stmt); + + using ODBC::ODBCStatement; + // The driver is built to handle SELECT statements only. + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + + statement->ExecutePrepared(); + + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLFetch(SQLHSTMT stmt) { + LOG_DEBUG("SQLFetch called with stmt: {}", stmt); + + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + + // The SQL_ATTR_ROW_ARRAY_SIZE statement attribute specifies the number of rows in the + // rowset. + ODBCDescriptor* ard = statement->GetARD(); + size_t rows = static_cast(ard->GetArraySize()); + if (statement->Fetch(rows)) { + return SQL_SUCCESS; + } else { + // Reached the end of rowset + return SQL_NO_DATA; + } + }); +} + +SQLRETURN SQLExtendedFetch(SQLHSTMT stmt, SQLUSMALLINT fetchOrientation, + SQLLEN fetchOffset, SQLULEN* rowCountPtr, + SQLUSMALLINT* rowStatusArray) { + // GH-47110: SQLExtendedFetch should return SQL_SUCCESS_WITH_INFO for certain diag + // states + LOG_DEBUG( + "SQLExtendedFetch called with stmt: {}, fetchOrientation: {}, fetchOffset: {}, " + "rowCountPtr: {}, rowStatusArray: {}", + stmt, fetchOrientation, fetchOffset, fmt::ptr(rowCountPtr), + fmt::ptr(rowStatusArray)); + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + if (fetchOrientation != SQL_FETCH_NEXT) { + throw DriverException("Optional feature not supported.", "HYC00"); + } + // fetchOffset is ignored as only SQL_FETCH_NEXT is supported + + ODBCStatement* statement = reinterpret_cast(stmt); + + // The SQL_ROWSET_SIZE statement attribute specifies the number of rows in the + // rowset. + SQLULEN rowSetSize = statement->GetRowsetSize(); + LOG_DEBUG("SQL_ROWSET_SIZE value for SQLExtendedFetch: {}", rowSetSize); + if (statement->Fetch(static_cast(rowSetSize), rowCountPtr, rowStatusArray)) { + return SQL_SUCCESS; + } else { + // Reached the end of rowset + return SQL_NO_DATA; + } + }); +} + +SQLRETURN SQLFetchScroll(SQLHSTMT stmt, SQLSMALLINT fetchOrientation, + SQLLEN fetchOffset) { + LOG_DEBUG("SQLFetchScroll called with stmt: {}, fetchOrientation: {}, fetchOffset: {}", + stmt, fetchOrientation, fetchOffset); + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + if (fetchOrientation != SQL_FETCH_NEXT) { + throw DriverException("Optional feature not supported.", "HYC00"); + } + // fetchOffset is ignored as only SQL_FETCH_NEXT is supported + + ODBCStatement* statement = reinterpret_cast(stmt); + + // The SQL_ATTR_ROW_ARRAY_SIZE statement attribute specifies the number of rows in the + // rowset. + ODBCDescriptor* ard = statement->GetARD(); + size_t rows = static_cast(ard->GetArraySize()); + if (statement->Fetch(rows)) { + return SQL_SUCCESS; + } else { + // Reached the end of rowset + return SQL_NO_DATA; + } + }); +} + +SQLRETURN SQLBindCol(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, SQLLEN* indicatorPtr) { + LOG_DEBUG( + "SQLBindCol called with stmt: {}, recordNumber: {}, cType: {}, " + "dataPtr: {}, bufferLength: {}, strLen_or_IndPtr: {}", + stmt, recordNumber, cType, dataPtr, bufferLength, fmt::ptr(indicatorPtr)); + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + // GH-47021: implement driver to return indicator value when data pointer is null + ODBCStatement* statement = reinterpret_cast(stmt); + ODBCDescriptor* ard = statement->GetARD(); + ard->BindCol(recordNumber, cType, dataPtr, bufferLength, indicatorPtr); + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLCloseCursor(SQLHSTMT stmt) { + LOG_DEBUG("SQLCloseCursor called with stmt: {}", stmt); + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + + // Close cursor with suppressErrors set to false + statement->closeCursor(false); + + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLGetData(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, SQLLEN* indicatorPtr) { + // GH-46979: support SQL_C_GUID data type + // GH-46980: support Interval data types + // GH-46985: return warning message instead of error on float truncation case + LOG_DEBUG( + "SQLGetData called with stmt: {}, recordNumber: {}, cType: {}, " + "dataPtr: {}, bufferLength: {}, indicatorPtr: {}", + stmt, recordNumber, cType, dataPtr, bufferLength, fmt::ptr(indicatorPtr)); + + using ODBC::ODBCStatement; + + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + return statement->GetData(recordNumber, cType, dataPtr, bufferLength, indicatorPtr); + }); +} + +SQLRETURN SQLMoreResults(SQLHSTMT stmt) { + LOG_DEBUG("SQLMoreResults called with stmt: {}", stmt); + using ODBC::ODBCStatement; + // Multiple result sets not supported. Return SQL_NO_DATA by default. + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + return statement->getMoreResults(); + }); +} + +SQLRETURN SQLNumResultCols(SQLHSTMT stmt, SQLSMALLINT* columnCountPtr) { + LOG_DEBUG("SQLNumResultCols called with stmt: {}, columnCountPtr: {}", stmt, + fmt::ptr(columnCountPtr)); + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + statement->getColumnCount(columnCountPtr); + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLRowCount(SQLHSTMT stmt, SQLLEN* rowCountPtr) { + LOG_DEBUG("SQLRowCount called with stmt: {}, columnCountPtr: {}", stmt, + fmt::ptr(rowCountPtr)); + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + statement->getRowCount(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 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 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, + SQLWCHAR* columnName, SQLSMALLINT columnNameLength) { + // GH-47159: Return NUM_PREC_RADIX based on whether COLUMN_SIZE contains number of + // digits or bits + LOG_DEBUG( + "SQLColumnsW called with stmt: {}, catalogName: {}, catalogNameLength: " + "{}, " + "schemaName: {}, schemaNameLength: {}, tableName: {}, tableNameLength: {}, " + "columnName: {}, " + "columnNameLength: {}", + stmt, fmt::ptr(catalogName), catalogNameLength, fmt::ptr(schemaName), + schemaNameLength, fmt::ptr(tableName), tableNameLength, fmt::ptr(columnName), + columnNameLength); + + using ODBC::ODBCStatement; + using ODBC::SqlWcharToString; + + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + + std::string catalog = SqlWcharToString(catalogName, catalogNameLength); + std::string schema = SqlWcharToString(schemaName, schemaNameLength); + std::string table = SqlWcharToString(tableName, tableNameLength); + std::string column = SqlWcharToString(columnName, columnNameLength); + + statement->GetColumns(catalogName ? &catalog : nullptr, + schemaName ? &schema : nullptr, tableName ? &table : nullptr, + columnName ? &column : nullptr); + + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT recordNumber, + SQLUSMALLINT fieldIdentifier, SQLPOINTER characterAttributePtr, + SQLSMALLINT bufferLength, SQLSMALLINT* outputLength, + SQLLEN* numericAttributePtr) { + LOG_DEBUG( + "SQLColAttributeW called with stmt: {}, recordNumber: {}, " + "fieldIdentifier: {}, characterAttributePtr: {}, bufferLength: {}, " + "outputLength: {}, numericAttributePtr: {}", + stmt, recordNumber, fieldIdentifier, characterAttributePtr, bufferLength, + fmt::ptr(outputLength), fmt::ptr(numericAttributePtr)); + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + ODBCDescriptor* ird = statement->GetIRD(); + SQLINTEGER outputLengthInt; + switch (fieldIdentifier) { + // Numeric attributes + // internal is SQLLEN, no conversion is needed + case SQL_DESC_DISPLAY_SIZE: + case SQL_DESC_OCTET_LENGTH: { + ird->GetField(recordNumber, fieldIdentifier, numericAttributePtr, bufferLength, + &outputLengthInt); + break; + } + // internal is SQLULEN, conversion is needed. + case SQL_COLUMN_LENGTH: // ODBC 2.0 + case SQL_DESC_LENGTH: { + SQLULEN temp; + ird->GetField(recordNumber, fieldIdentifier, &temp, bufferLength, + &outputLengthInt); + if (numericAttributePtr) { + *numericAttributePtr = static_cast(temp); + } + break; + } + // internal is SQLINTEGER, conversion is needed. + case SQL_DESC_AUTO_UNIQUE_VALUE: + case SQL_DESC_CASE_SENSITIVE: + case SQL_DESC_NUM_PREC_RADIX: { + SQLINTEGER temp; + ird->GetField(recordNumber, fieldIdentifier, &temp, bufferLength, + &outputLengthInt); + if (numericAttributePtr) { + *numericAttributePtr = static_cast(temp); + } + break; + } + // internal is SQLSMALLINT, conversion is needed. + case SQL_DESC_CONCISE_TYPE: + case SQL_DESC_COUNT: + case SQL_DESC_FIXED_PREC_SCALE: + case SQL_DESC_TYPE: + case SQL_DESC_NULLABLE: + case SQL_COLUMN_PRECISION: // ODBC 2.0 + case SQL_DESC_PRECISION: + case SQL_COLUMN_SCALE: // ODBC 2.0 + case SQL_DESC_SCALE: + case SQL_DESC_SEARCHABLE: + case SQL_DESC_UNNAMED: + case SQL_DESC_UNSIGNED: + case SQL_DESC_UPDATABLE: { + SQLSMALLINT temp; + ird->GetField(recordNumber, fieldIdentifier, &temp, bufferLength, + &outputLengthInt); + if (numericAttributePtr) { + *numericAttributePtr = static_cast(temp); + } + break; + } + // Character attributes + case SQL_DESC_BASE_COLUMN_NAME: + case SQL_DESC_BASE_TABLE_NAME: + case SQL_DESC_CATALOG_NAME: + case SQL_DESC_LABEL: + case SQL_DESC_LITERAL_PREFIX: + case SQL_DESC_LITERAL_SUFFIX: + case SQL_DESC_LOCAL_TYPE_NAME: + case SQL_DESC_NAME: + case SQL_DESC_SCHEMA_NAME: + case SQL_DESC_TABLE_NAME: + case SQL_DESC_TYPE_NAME: + ird->GetField(recordNumber, fieldIdentifier, characterAttributePtr, bufferLength, + &outputLengthInt); + break; + default: + throw DriverException("Invalid descriptor field", "HY091"); + } + if (outputLength) { + *outputLength = static_cast(outputLengthInt); + } + return SQL_SUCCESS; + }); +} + +SQLRETURN SQLNativeSql(SQLHDBC connectionHandle, SQLWCHAR* inStatementText, + SQLINTEGER inStatementTextLength, SQLWCHAR* outStatementText, + SQLINTEGER bufferLength, SQLINTEGER* outStatementTextLength) { + LOG_DEBUG( + "SQLNativeSqlW called with connectionHandle: {}, inStatementText: {}, " + "inStatementTextLength: {}, outStatementText: {}, bufferLength: {}, " + "outStatementTextLength: {}", + connectionHandle, fmt::ptr(inStatementText), inStatementTextLength, + fmt::ptr(outStatementText), bufferLength, fmt::ptr(outStatementTextLength)); + + using driver::odbcabstraction::Diagnostics; + using ODBC::GetAttributeSQLWCHAR; + using ODBC::ODBCConnection; + using ODBC::SqlWcharToString; + + return ODBCConnection::ExecuteWithDiagnostics(connectionHandle, SQL_ERROR, [=]() { + const bool isLengthInBytes = false; + + ODBCConnection* connection = reinterpret_cast(connectionHandle); + Diagnostics& diagnostics = connection->GetDiagnostics(); + + std::string inStatementStr = SqlWcharToString(inStatementText, inStatementTextLength); + + return GetAttributeSQLWCHAR(inStatementStr, isLengthInBytes, outStatementText, + bufferLength, outStatementTextLength, diagnostics); + }); +} + +SQLRETURN SQLDescribeCol(SQLHSTMT stmt, SQLUSMALLINT columnNumber, SQLWCHAR* columnName, + SQLSMALLINT bufferLength, SQLSMALLINT* nameLengthPtr, + SQLSMALLINT* dataTypePtr, SQLULEN* columnSizePtr, + SQLSMALLINT* decimalDigitsPtr, SQLSMALLINT* nullablePtr) { + LOG_DEBUG( + "SQLDescribeColW called with stmt: {}, columnNumber: {}, " + "columnName: {}, bufferLength: {}, nameLengthPtr: {}, dataTypePtr: {}, " + "columnSizePtr: {}, decimalDigitsPtr: {}, nullablePtr: {}", + stmt, columnNumber, fmt::ptr(columnName), bufferLength, fmt::ptr(nameLengthPtr), + fmt::ptr(dataTypePtr), fmt::ptr(columnSizePtr), fmt::ptr(decimalDigitsPtr), + fmt::ptr(nullablePtr)); + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + ODBCDescriptor* ird = statement->GetIRD(); + + SQLSMALLINT sqlType; + ird->GetField(columnNumber, SQL_DESC_CONCISE_TYPE, &sqlType, sizeof(SQLSMALLINT), + nullptr); + + SQLINTEGER outputLengthInt; + ird->GetField(columnNumber, SQL_DESC_NAME, columnName, bufferLength, + &outputLengthInt); + if (nameLengthPtr) { + *nameLengthPtr = static_cast(outputLengthInt); + } + + if (dataTypePtr) { + *dataTypePtr = sqlType; + } + + // Column Size + if (columnSizePtr) { + switch (sqlType) { + // All numeric types + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: { + ird->GetField(columnNumber, SQL_DESC_PRECISION, columnSizePtr, sizeof(SQLULEN), + nullptr); + break; + } + + default: { + ird->GetField(columnNumber, SQL_DESC_LENGTH, columnSizePtr, sizeof(SQLULEN), + nullptr); + } + } + } + + // Decimal Digits + if (decimalDigitsPtr) { + switch (sqlType) { + // All exact numeric types + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_DECIMAL: + case SQL_NUMERIC: { + ird->GetField(columnNumber, SQL_DESC_SCALE, decimalDigitsPtr, sizeof(SQLULEN), + nullptr); + break; + } + + // All datetime types + case SQL_DATE: + case SQL_TIME: + case SQL_TIMESTAMP: + // TODO THESE ARE UNDEFINED - ODBC 3 Only Datetime Types + // SQL_TYPE_TIMESTAMP_WITH_TIMEZONE and SQL_TYPE_TIME_WITH_TIMEZONE are rarely + // used and not universally supported. + // case SQL_TYPE_TIME_WITH_TIMEZONE: + // case SQL_TYPE_TIMESTAMP_WITH_TIMEZONE: + // + // All interval types with a seconds component + case SQL_INTERVAL_SECOND: + case SQL_INTERVAL_MINUTE_TO_SECOND: + case SQL_INTERVAL_HOUR_TO_SECOND: + case SQL_INTERVAL_DAY_TO_SECOND: { + ird->GetField(columnNumber, SQL_DESC_PRECISION, decimalDigitsPtr, + sizeof(SQLULEN), nullptr); + break; + } + + default: { + // All character and binary types + // SQL_BIT + // All approximate numeric types + // All interval types with no seconds component + *decimalDigitsPtr = static_cast(0); + } + } + } + + // Nullable + ird->GetField(columnNumber, SQL_DESC_NULLABLE, nullablePtr, sizeof(SQLSMALLINT), + 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 new file mode 100644 index 000000000000..181764c1f22a --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.h @@ -0,0 +1,103 @@ +// 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. + +#pragma once + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include + +// @file odbc_api.h +// +// Define internal ODBC API function headers. +namespace arrow { +SQLRETURN SQLAllocHandle(SQLSMALLINT type, SQLHANDLE parent, SQLHANDLE* result); +SQLRETURN SQLFreeHandle(SQLSMALLINT type, SQLHANDLE handle); +SQLRETURN SQLFreeStmt(SQLHSTMT stmt, SQLUSMALLINT option); +SQLRETURN SQLGetDiagField(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLINT recNumber, + SQLSMALLINT diagIdentifier, SQLPOINTER diagInfoPtr, + SQLSMALLINT bufferLength, SQLSMALLINT* stringLengthPtr); +SQLRETURN SQLGetDiagRec(SQLSMALLINT handleType, SQLHANDLE handle, SQLSMALLINT recNumber, + SQLWCHAR* sqlState, SQLINTEGER* nativeErrorPtr, + SQLWCHAR* messageText, SQLSMALLINT bufferLength, + SQLSMALLINT* textLengthPtr); +SQLRETURN SQLGetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER valuePtr, + SQLINTEGER bufferLen, SQLINTEGER* strLenPtr); +SQLRETURN SQLSetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER valuePtr, + SQLINTEGER strLen); +SQLRETURN SQLGetConnectAttr(SQLHDBC conn, SQLINTEGER attribute, SQLPOINTER valuePtr, + SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr); +SQLRETURN SQLSetConnectAttr(SQLHDBC conn, SQLINTEGER attr, SQLPOINTER value, + SQLINTEGER valueLen); +SQLRETURN SQLDriverConnect(SQLHDBC conn, SQLHWND windowHandle, + SQLWCHAR* inConnectionString, + SQLSMALLINT inConnectionStringLen, + SQLWCHAR* outConnectionString, + SQLSMALLINT outConnectionStringBufferLen, + SQLSMALLINT* outConnectionStringLen, + SQLUSMALLINT driverCompletion); +SQLRETURN SQLConnect(SQLHDBC conn, SQLWCHAR* dsnName, SQLSMALLINT dsnNameLen, + SQLWCHAR* userName, SQLSMALLINT userNameLen, SQLWCHAR* password, + SQLSMALLINT passwordLen); +SQLRETURN SQLDisconnect(SQLHDBC conn); +SQLRETURN SQLGetInfo(SQLHDBC conn, SQLUSMALLINT infoType, SQLPOINTER infoValuePtr, + SQLSMALLINT bufLen, SQLSMALLINT* length); +SQLRETURN SQLGetStmtAttr(SQLHSTMT stmt, SQLINTEGER attribute, SQLPOINTER valuePtr, + SQLINTEGER bufferLength, SQLINTEGER* stringLengthPtr); +SQLRETURN SQLSetStmtAttr(SQLHSTMT stmt, SQLINTEGER attribute, SQLPOINTER valuePtr, + SQLINTEGER stringLength); +SQLRETURN SQLExecDirect(SQLHSTMT stmt, SQLWCHAR* queryText, SQLINTEGER textLength); +SQLRETURN SQLPrepare(SQLHSTMT stmt, SQLWCHAR* queryText, SQLINTEGER textLength); +SQLRETURN SQLExecute(SQLHSTMT stmt); +SQLRETURN SQLFetch(SQLHSTMT stmt); +SQLRETURN SQLExtendedFetch(SQLHSTMT stmt, SQLUSMALLINT fetchOrientation, + SQLLEN fetchOffset, SQLULEN* rowCountPtr, + SQLUSMALLINT* rowStatusArray); +SQLRETURN SQLFetchScroll(SQLHSTMT stmt, SQLSMALLINT fetchOrientation, SQLLEN fetchOffset); +SQLRETURN SQLBindCol(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, SQLLEN* indicatorPtr); +SQLRETURN SQLCloseCursor(SQLHSTMT stmt); +SQLRETURN SQLGetData(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, SQLLEN* indicatorPtr); +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, + SQLWCHAR* columnName, SQLSMALLINT columnNameLength); +SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT recordNumber, + SQLUSMALLINT fieldIdentifier, SQLPOINTER characterAttributePtr, + SQLSMALLINT bufferLength, SQLSMALLINT* outputLength, + SQLLEN* numericAttributePtr); +SQLRETURN SQLNativeSql(SQLHDBC connectionHandle, SQLWCHAR* inStatementText, + SQLINTEGER inStatementTextLength, SQLWCHAR* outStatementText, + SQLINTEGER bufferLength, SQLINTEGER* outStatementTextLength); +SQLRETURN SQLDescribeCol(SQLHSTMT statementHandle, SQLUSMALLINT columnNumber, + SQLWCHAR* columnName, SQLSMALLINT bufferLength, + SQLSMALLINT* nameLengthPtr, SQLSMALLINT* dataTypePtr, + SQLULEN* columnSizePtr, SQLSMALLINT* decimalDigitsPtr, + SQLSMALLINT* nullablePtr); +} // namespace arrow diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/CMakeLists.txt index c9614b88a5b5..dd8b6dd2f1e6 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/CMakeLists.txt @@ -17,9 +17,6 @@ include_directories(include) -# Ensure fmt is loaded as header only -add_compile_definitions(FMT_HEADER_ONLY) - add_library(odbcabstraction include/odbcabstraction/calendar_utils.h include/odbcabstraction/diagnostics.h @@ -66,19 +63,4 @@ set_target_properties(odbcabstraction RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$/lib) -include(FetchContent) -fetchcontent_declare(spdlog - URL https://github.com/gabime/spdlog/archive/76fb40d95455f249bd70824ecfcae7a8f0930fa3.zip - CONFIGURE_COMMAND - "" - BUILD_COMMAND - "") -fetchcontent_getproperties(spdlog) -if(NOT spdlog_POPULATED) - fetchcontent_populate(spdlog) -endif() - -add_library(spdlog INTERFACE) -target_include_directories(spdlog INTERFACE ${spdlog_SOURCE_DIR}/include) - -target_link_libraries(odbcabstraction PUBLIC spdlog) +target_link_libraries(odbcabstraction PUBLIC spdlog::spdlog) diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/calendar_utils.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/calendar_utils.cc index f4a23419f1ab..d81487866adb 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/calendar_utils.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/calendar_utils.cc @@ -17,6 +17,7 @@ #include "odbcabstraction/calendar_utils.h" +#include #include #include @@ -40,12 +41,9 @@ int64_t GetTodayTimeFromEpoch() { } void GetTimeForSecondsSinceEpoch(tm& date, int64_t value) { -#if defined(_WIN32) - gmtime_s(&date, &value); -#else - time_t time_value = static_cast(value); - gmtime_r(&time_value, &date); -#endif + // Boost date-time library only support years from range 1400-9999 + // GH-46978: support years before 1400 for date, time, and timestamp types + date = boost::posix_time::to_tm(boost::posix_time::from_time_t(value)); } } // namespace odbcabstraction } // namespace driver diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/diagnostics.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/diagnostics.cc index 8c94978ef997..78ca45ea2fe2 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/diagnostics.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/diagnostics.cc @@ -15,9 +15,9 @@ // specific language governing permissions and limitations // under the License. -#include -#include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" #include diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/encoding.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/encoding.cc index 95dc920da78f..00718cdbbe56 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/encoding.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/encoding.cc @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/encoding.h" #if defined(__APPLE__) # include diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/exceptions.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/exceptions.cc index fcd8163a500c..242c85e5a28f 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/exceptions.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/exceptions.cc @@ -15,8 +15,9 @@ // specific language governing permissions and limitations // under the License. -#include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" + #include namespace driver { diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h index f1c6efe49829..473411efd4f3 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h @@ -21,8 +21,8 @@ #include #include -#include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" namespace driver { namespace odbcabstraction { diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h index 48a773e4f4df..82ffebedff6b 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h @@ -17,10 +17,10 @@ #pragma once -#include #include #include #include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/error_codes.h" namespace driver { namespace odbcabstraction { diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/logger.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/logger.h index 5f8619cbb921..6249df98834a 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/logger.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/logger.h @@ -18,9 +18,13 @@ #pragma once #include +#include #include -#include +#include + +// The logger using spdlog is deprecated and will be replaced. +// TODO: mirgate logging to use Arrow's internal logging system #define __LAZY_LOG(LEVEL, ...) \ do { \ diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h index 9163e942cebd..d194ace237f8 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h @@ -17,16 +17,16 @@ #pragma once -#include -#include -#include #include #include #include #include #include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/encoding_utils.h" namespace ODBC { using driver::odbcabstraction::WcsToUtf8; @@ -45,12 +45,12 @@ inline void GetAttribute(T attributeValue, SQLPOINTER output, O outputSize, } template -inline SQLRETURN GetAttributeUTF8(const std::string& attributeValue, SQLPOINTER output, - O outputSize, O* outputLenPtr) { +inline SQLRETURN GetAttributeUTF8(const std::string_view& attributeValue, + SQLPOINTER output, O outputSize, O* outputLenPtr) { if (output) { size_t outputLenBeforeNul = std::min(static_cast(attributeValue.size()), static_cast(outputSize - 1)); - memcpy(output, attributeValue.c_str(), outputLenBeforeNul); + memcpy(output, attributeValue.data(), outputLenBeforeNul); reinterpret_cast(output)[outputLenBeforeNul] = '\0'; } @@ -65,8 +65,8 @@ inline SQLRETURN GetAttributeUTF8(const std::string& attributeValue, SQLPOINTER } template -inline SQLRETURN GetAttributeUTF8(const std::string& attributeValue, SQLPOINTER output, - O outputSize, O* outputLenPtr, +inline SQLRETURN GetAttributeUTF8(const std::string_view& attributeValue, + SQLPOINTER output, O outputSize, O* outputLenPtr, driver::odbcabstraction::Diagnostics& diagnostics) { SQLRETURN result = GetAttributeUTF8(attributeValue, output, outputSize, outputLenPtr); if (SQL_SUCCESS_WITH_INFO == result) { @@ -76,26 +76,30 @@ inline SQLRETURN GetAttributeUTF8(const std::string& attributeValue, SQLPOINTER } template -inline SQLRETURN GetAttributeSQLWCHAR(const std::string& attributeValue, +inline SQLRETURN GetAttributeSQLWCHAR(const std::string_view& attributeValue, bool isLengthInBytes, SQLPOINTER output, O outputSize, O* outputLenPtr) { - size_t result = + size_t length = ConvertToSqlWChar(attributeValue, reinterpret_cast(output), isLengthInBytes ? outputSize : outputSize * GetSqlWCharSize()); + if (!isLengthInBytes) { + length = length / GetSqlWCharSize(); + } + if (outputLenPtr) { - *outputLenPtr = static_cast(isLengthInBytes ? result : result / GetSqlWCharSize()); + *outputLenPtr = static_cast(length); } if (output && - outputSize < static_cast(result + (isLengthInBytes ? GetSqlWCharSize() : 1))) { + outputSize < static_cast(length + (isLengthInBytes ? GetSqlWCharSize() : 1))) { return SQL_SUCCESS_WITH_INFO; } return SQL_SUCCESS; } template -inline SQLRETURN GetAttributeSQLWCHAR(const std::string& attributeValue, +inline SQLRETURN GetAttributeSQLWCHAR(const std::string_view& attributeValue, bool isLengthInBytes, SQLPOINTER output, O outputSize, O* outputLenPtr, driver::odbcabstraction::Diagnostics& diagnostics) { @@ -108,7 +112,8 @@ inline SQLRETURN GetAttributeSQLWCHAR(const std::string& attributeValue, } template -inline SQLRETURN GetStringAttribute(bool isUnicode, const std::string& attributeValue, +inline SQLRETURN GetStringAttribute(bool isUnicode, + const std::string_view& attributeValue, bool isLengthInBytes, SQLPOINTER output, O outputSize, O* outputLenPtr, driver::odbcabstraction::Diagnostics& diagnostics) { diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/encoding_utils.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/encoding_utils.h index 25619bb55551..94f4569ba890 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/encoding_utils.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/encoding_utils.h @@ -16,9 +16,9 @@ // under the License. #pragma once +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/encoding.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" -#include -#include #include #include #include @@ -34,10 +34,11 @@ namespace ODBC { using driver::odbcabstraction::DriverException; using driver::odbcabstraction::GetSqlWCharSize; using driver::odbcabstraction::Utf8ToWcs; +using driver::odbcabstraction::WcsToUtf8; // Return the number of bytes required for the conversion. template -inline size_t ConvertToSqlWChar(const std::string& str, SQLWCHAR* buffer, +inline size_t ConvertToSqlWChar(const std::string_view& str, SQLWCHAR* buffer, SQLLEN bufferSizeInBytes) { thread_local std::vector wstr; Utf8ToWcs(str.data(), str.size(), &wstr); @@ -63,7 +64,7 @@ inline size_t ConvertToSqlWChar(const std::string& str, SQLWCHAR* buffer, return valueLengthInBytes; } -inline size_t ConvertToSqlWChar(const std::string& str, SQLWCHAR* buffer, +inline size_t ConvertToSqlWChar(const std::string_view& str, SQLWCHAR* buffer, SQLLEN bufferSizeInBytes) { switch (GetSqlWCharSize()) { case sizeof(char16_t): @@ -77,4 +78,39 @@ inline size_t ConvertToSqlWChar(const std::string& str, SQLWCHAR* buffer, } } +/// \brief Convert buffer of SqlWchar to standard string +/// \param[in] wchar_msg SqlWchar to convert +/// \param[in] msg_len Number of characters in wchar_msg +/// \return wchar_msg in std::string format +inline std::string SqlWcharToString(SQLWCHAR* wchar_msg, SQLINTEGER msg_len = SQL_NTS) { + if (!wchar_msg || wchar_msg[0] == 0 || msg_len == 0) { + return std::string(); + } + + thread_local std::vector utf8_str; + + if (msg_len == SQL_NTS) { + WcsToUtf8((void*)wchar_msg, &utf8_str); + } else { + WcsToUtf8((void*)wchar_msg, msg_len, &utf8_str); + } + + return std::string(utf8_str.begin(), utf8_str.end()); +} + +inline std::string SqlStringToString(const unsigned char* sqlStr, + int32_t sqlStrLen = SQL_NTS) { + std::string res; + + const char* sqlStrC = reinterpret_cast(sqlStr); + + if (!sqlStr) return res; + + if (sqlStrLen == SQL_NTS) + res.assign(sqlStrC); + else if (sqlStrLen > 0) + res.assign(sqlStrC, sqlStrLen); + + return res; +} } // namespace ODBC diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h index 6a01fe128d90..0e9498bcb8a0 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h @@ -17,9 +17,9 @@ #pragma once -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_handle.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h" -#include #include #include #include @@ -41,6 +41,9 @@ class ODBCConnection : public ODBCHandle { ODBCConnection(const ODBCConnection&) = delete; ODBCConnection& operator=(const ODBCConnection&) = delete; + /// \brief Constructor for ODBCConnection. + /// \param[in] environment the parent environment. + /// \param[in] spiConnection the underlying spi connection. ODBCConnection(ODBCEnvironment& environment, std::shared_ptr spiConnection); @@ -48,16 +51,22 @@ class ODBCConnection : public ODBCHandle { const std::string& GetDSN() const; bool isConnected() const; + + /// \brief Connect to Arrow Flight SQL server. + /// \param[in] dsn the dsn name. + /// \param[in] properties the connection property map extracted from connection string. + /// \param[out] missing_properties report the properties that are missing void connect(std::string dsn, const driver::odbcabstraction::Connection::ConnPropertyMap& properties, std::vector& missing_properties); - void GetInfo(SQLUSMALLINT infoType, SQLPOINTER value, SQLSMALLINT bufferLength, - SQLSMALLINT* outputLength, bool isUnicode); + SQLRETURN GetInfo(SQLUSMALLINT infoType, SQLPOINTER value, SQLSMALLINT bufferLength, + SQLSMALLINT* outputLength, bool isUnicode); void SetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER stringLength, bool isUnicode); - void GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER bufferLength, - SQLINTEGER* outputLength, bool isUnicode); + SQLRETURN GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, + SQLINTEGER bufferLength, SQLINTEGER* outputLength, + bool isUnicode); ~ODBCConnection() = default; diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h index 092483f47199..e7656082c5ce 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h @@ -17,7 +17,7 @@ #pragma once -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_handle.h" #include #include diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_handle.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_handle.h index c2428df394d9..64257541a873 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_handle.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_handle.h @@ -17,12 +17,14 @@ #pragma once -#include -#include +// platform.h includes windows.h, so it needs to be included first +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" + #include #include #include #include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h" /** * @brief An abstraction over a generic ODBC handle. @@ -47,7 +49,7 @@ class ODBCHandle { rc = function(); } catch (const driver::odbcabstraction::DriverException& ex) { GetDiagnostics().AddError(ex); - } catch (const std::bad_alloc& ex) { + } catch (const std::bad_alloc&) { GetDiagnostics().AddError(driver::odbcabstraction::DriverException( "A memory allocation error occurred.", "HY001")); } catch (const std::exception& ex) { diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h index bbddfac4185a..73cdc2448f8f 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h @@ -17,9 +17,11 @@ #pragma once -#include +// platform.h platform.h includes windows.h so it needs to be included first +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" + +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_handle.h" -#include #include #include #include @@ -64,8 +66,10 @@ class ODBCStatement : public ODBCHandle { /** * @brief Returns true if the number of rows fetch was greater than zero. + * rowCountPtr and rowStatusArray are optional arguments, they are only needed for + * SQLExtendedFetch */ - bool Fetch(size_t rows); + bool Fetch(size_t rows, SQLULEN* rowCountPtr = 0, SQLUSMALLINT* rowStatusArray = 0); bool isPrepared() const; void GetStmtAttr(SQLINTEGER statementAttribute, SQLPOINTER output, @@ -81,8 +85,20 @@ class ODBCStatement : public ODBCHandle { inline SQLULEN GetRowsetSize() { return m_rowsetSize; } - bool GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, SQLPOINTER dataPtr, - SQLLEN bufferLength, SQLLEN* indicatorPtr); + SQLRETURN GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, SQLPOINTER dataPtr, + SQLLEN bufferLength, SQLLEN* indicatorPtr); + + SQLRETURN getMoreResults(); + + /** + * @brief Get number of columns from data set + */ + void getColumnCount(SQLSMALLINT* columnCountPtr); + + /** + * @brief Get number of rows affected by an UPDATE, INSERT, or DELETE statement + */ + void getRowCount(SQLLEN* rowCountPtr); /** * @brief Closes the cursor. This does _not_ un-prepare the statement or change diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h index 792a52c1fadf..ce86882c9524 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h @@ -25,21 +25,21 @@ #include #include -#include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" namespace driver { namespace odbcabstraction { /// \brief Case insensitive comparator struct CaseInsensitiveComparator { - bool operator()(const std::string_view& s1, const std::string_view& s2) const { + bool operator()(const std::string& s1, const std::string& s2) const { return boost::lexicographical_compare(s1, s2, boost::is_iless()); } }; // PropertyMap is case-insensitive for keys. -typedef std::map PropertyMap; +typedef std::map PropertyMap; class Statement; diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/driver.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/driver.h index f13371bf2d55..61d570574c7f 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/driver.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/driver.h @@ -19,8 +19,8 @@ #include -#include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" namespace driver { namespace odbcabstraction { diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h index 1b3f8eb96d86..c24c64248608 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h @@ -20,9 +20,11 @@ #include #include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" + +#include namespace driver { namespace odbcabstraction { @@ -88,10 +90,10 @@ class ResultSet { /// \param buffer Target buffer to be populated. /// \param buffer_length Target buffer length. /// \param strlen_buffer Buffer that holds the length of value being fetched. - /// \returns true if there is more data to fetch from the current cell; - /// false if the whole value was already fetched. - virtual bool GetData(int column, int16_t target_type, int precision, int scale, - void* buffer, size_t buffer_length, ssize_t* strlen_buffer) = 0; + /// \returns SQLRETURN for SQLGetData. + virtual SQLRETURN GetData(int column, int16_t target_type, int precision, int scale, + void* buffer, size_t buffer_length, + ssize_t* strlen_buffer) = 0; }; } // namespace odbcabstraction diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set_metadata.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set_metadata.h index f625a2598c12..636dce21e4a7 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set_metadata.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set_metadata.h @@ -17,8 +17,8 @@ #pragma once -#include #include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" namespace driver { namespace odbcabstraction { diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h index e5d206a2ca7c..8f16000daaa1 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h @@ -17,8 +17,8 @@ #pragma once -#include #include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h" namespace driver { namespace odbcabstraction { diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/utils.h b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/utils.h index cc848baa0fde..6e1fe5739be4 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/utils.h +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/utils.h @@ -17,10 +17,10 @@ #pragma once -#include -#include #include #include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/logger.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h" namespace driver { namespace odbcabstraction { @@ -52,7 +52,8 @@ boost::optional AsInt32(int32_t min_value, const Connection::ConnPropertyMap& connPropertyMap, const std::string_view& property_name); -void ReadConfigFile(PropertyMap& properties, const std::string& configFileName); +void ReadConfigFile(PropertyMap& properties, const std::string& configPath, + const std::string& configFileName); } // namespace odbcabstraction } // namespace driver diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/logger.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/logger.cc index edace64cf6ad..8b105a2f0b68 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/logger.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/logger.cc @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/logger.h" namespace driver { namespace odbcabstraction { diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_connection.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_connection.cc index 0143976bb488..337951ede3ac 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_connection.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_connection.cc @@ -17,6 +17,10 @@ #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h" +#include "arrow/result.h" +#include "arrow/util/utf8.h" + +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h" #include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h" @@ -49,48 +53,16 @@ namespace { // characters such as semi-colons and equals signs. NOTE: This can be optimized to be // built statically. const boost::xpressive::sregex CONNECTION_STR_REGEX( - boost::xpressive::sregex::compile("([^=;]+)=({.+}|[^=;]+|[^;])")); + boost::xpressive::sregex::compile("([^=;]+)=({.+}|[^;]+|[^;])")); // Load properties from the given DSN. The properties loaded do _not_ overwrite existing // entries in the properties. void loadPropertiesFromDSN(const std::string& dsn, Connection::ConnPropertyMap& properties) { - const size_t BUFFER_SIZE = 1024 * 10; - std::vector outputBuffer; - outputBuffer.resize(BUFFER_SIZE, '\0'); - SQLSetConfigMode(ODBC_BOTH_DSN); - - SQLGetPrivateProfileString(dsn.c_str(), NULL, "", &outputBuffer[0], BUFFER_SIZE, - "odbc.ini"); - - // The output buffer holds the list of keys in a series of NUL-terminated strings. - // The series is terminated with an empty string (eg a NUL-terminator terminating the - // last key followed by a NUL terminator after). - std::vector keys; - size_t pos = 0; - while (pos < BUFFER_SIZE) { - std::string key(&outputBuffer[pos]); - if (key.empty()) { - break; - } - size_t len = key.size(); - - // Skip over Driver or DSN keys. - if (!boost::iequals(key, "DSN") && !boost::iequals(key, "Driver")) { - keys.emplace_back(std::move(key)); - } - pos += len + 1; - } - - for (auto& key : keys) { - outputBuffer.clear(); - outputBuffer.resize(BUFFER_SIZE, '\0'); - - std::string key_str = std::string(key); - SQLGetPrivateProfileString(dsn.c_str(), key_str.c_str(), "", &outputBuffer[0], - BUFFER_SIZE, "odbc.ini"); - - std::string value = std::string(&outputBuffer[0]); + driver::flight_sql::config::Configuration config; + config.LoadDsn(dsn); + Connection::ConnPropertyMap dsnProperties = config.GetProperties(); + for (auto& [key, value] : dsnProperties) { auto propIter = properties.find(key); if (propIter == properties.end()) { properties.emplace(std::make_pair(std::move(key), std::move(value))); @@ -131,152 +103,144 @@ void ODBCConnection::connect(std::string dsn, m_attributeTrackingStatement = std::make_shared(*this, spiStatement); } -void ODBCConnection::GetInfo(SQLUSMALLINT infoType, SQLPOINTER value, - SQLSMALLINT bufferLength, SQLSMALLINT* outputLength, - bool isUnicode) { +SQLRETURN ODBCConnection::GetInfo(SQLUSMALLINT infoType, SQLPOINTER value, + SQLSMALLINT bufferLength, SQLSMALLINT* outputLength, + bool isUnicode) { switch (infoType) { case SQL_ACTIVE_ENVIRONMENTS: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; #ifdef SQL_ASYNC_DBC_FUNCTIONS case SQL_ASYNC_DBC_FUNCTIONS: GetAttribute(static_cast(SQL_ASYNC_DBC_NOT_CAPABLE), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; #endif case SQL_ASYNC_MODE: GetAttribute(static_cast(SQL_AM_NONE), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; #ifdef SQL_ASYNC_NOTIFICATION case SQL_ASYNC_NOTIFICATION: GetAttribute(static_cast(SQL_ASYNC_NOTIFICATION_NOT_CAPABLE), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; #endif case SQL_BATCH_ROW_COUNT: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_BATCH_SUPPORT: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_DATA_SOURCE_NAME: - GetStringAttribute(isUnicode, m_dsn, true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, m_dsn, true, value, bufferLength, outputLength, + GetDiagnostics()); case SQL_DRIVER_ODBC_VER: - GetStringAttribute(isUnicode, "03.80", true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, "03.80", true, value, bufferLength, + outputLength, GetDiagnostics()); case SQL_DYNAMIC_CURSOR_ATTRIBUTES1: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_DYNAMIC_CURSOR_ATTRIBUTES2: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1: GetAttribute(static_cast(SQL_CA1_NEXT), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2: GetAttribute(static_cast(SQL_CA2_READ_ONLY_CONCURRENCY), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_FILE_USAGE: GetAttribute(static_cast(SQL_FILE_NOT_SUPPORTED), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_KEYSET_CURSOR_ATTRIBUTES1: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_KEYSET_CURSOR_ATTRIBUTES2: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_MAX_ASYNC_CONCURRENT_STATEMENTS: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_ODBC_INTERFACE_CONFORMANCE: GetAttribute(static_cast(SQL_OIC_CORE), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; // case SQL_ODBC_STANDARD_CLI_CONFORMANCE: - mentioned in SQLGetInfo spec with no // description and there is no constant for this. case SQL_PARAM_ARRAY_ROW_COUNTS: GetAttribute(static_cast(SQL_PARC_NO_BATCH), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_PARAM_ARRAY_SELECTS: GetAttribute(static_cast(SQL_PAS_NO_SELECT), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_ROW_UPDATES: - GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, + GetDiagnostics()); case SQL_SCROLL_OPTIONS: GetAttribute(static_cast(SQL_SO_FORWARD_ONLY), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_STATIC_CURSOR_ATTRIBUTES1: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_STATIC_CURSOR_ATTRIBUTES2: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_BOOKMARK_PERSISTENCE: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_DESCRIBE_PARAMETER: - GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, + GetDiagnostics()); case SQL_MULT_RESULT_SETS: - GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, + GetDiagnostics()); case SQL_MULTIPLE_ACTIVE_TXN: - GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, + GetDiagnostics()); case SQL_NEED_LONG_DATA_LEN: - GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, "N", true, value, bufferLength, outputLength, + GetDiagnostics()); case SQL_TXN_CAPABLE: GetAttribute(static_cast(SQL_TC_NONE), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_TXN_ISOLATION_OPTION: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_TABLE_TERM: - GetStringAttribute(isUnicode, "table", true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, "table", true, value, bufferLength, + outputLength, GetDiagnostics()); // Deprecated ODBC 2.x fields required for backwards compatibility. case SQL_ODBC_API_CONFORMANCE: GetAttribute(static_cast(SQL_OAC_LEVEL1), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_FETCH_DIRECTION: GetAttribute(static_cast(SQL_FETCH_NEXT), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_LOCK_TYPES: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_POS_OPERATIONS: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_POSITIONED_STATEMENTS: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_SCROLL_CONCURRENCY: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; case SQL_STATIC_SENSITIVITY: GetAttribute(static_cast(0), value, bufferLength, outputLength); - break; + return SQL_SUCCESS; // Driver-level string properties. case SQL_USER_NAME: @@ -311,9 +275,8 @@ void ODBCConnection::GetInfo(SQLUSMALLINT infoType, SQLPOINTER value, case SQL_XOPEN_CLI_YEAR: { const auto& info = m_spiConnection->GetInfo(infoType); const std::string& infoValue = boost::get(info); - GetStringAttribute(isUnicode, infoValue, true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, infoValue, true, value, bufferLength, + outputLength, GetDiagnostics()); } // Driver-level 32-bit integer properties. @@ -403,7 +366,7 @@ void ODBCConnection::GetInfo(SQLUSMALLINT infoType, SQLPOINTER value, const auto& info = m_spiConnection->GetInfo(infoType); uint32_t infoValue = boost::get(info); GetAttribute(infoValue, value, bufferLength, outputLength); - break; + return SQL_SUCCESS; } // Driver-level 16-bit integer properties. @@ -438,7 +401,7 @@ void ODBCConnection::GetInfo(SQLUSMALLINT infoType, SQLPOINTER value, const auto& info = m_spiConnection->GetInfo(infoType); uint16_t infoValue = boost::get(info); GetAttribute(infoValue, value, bufferLength, outputLength); - break; + return SQL_SUCCESS; } // Special case - SQL_DATABASE_NAME is an alias for SQL_ATTR_CURRENT_CATALOG. @@ -448,13 +411,15 @@ void ODBCConnection::GetInfo(SQLUSMALLINT infoType, SQLPOINTER value, throw DriverException("Optional feature not supported.", "HYC00"); } const std::string& infoValue = boost::get(*attr); - GetStringAttribute(isUnicode, infoValue, true, value, bufferLength, outputLength, - GetDiagnostics()); - break; + return GetStringAttribute(isUnicode, infoValue, true, value, bufferLength, + outputLength, GetDiagnostics()); } default: - throw DriverException("Unknown SQLGetInfo type: " + std::to_string(infoType)); + throw DriverException("Unknown SQLGetInfo type: " + std::to_string(infoType), + "HY096"); } + + return SQL_ERROR; } void ODBCConnection::SetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, @@ -463,7 +428,7 @@ void ODBCConnection::SetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, bool successfully_written = false; switch (attribute) { // Internal connection attributes -#ifdef SQL_ATR_ASYNC_DBC_EVENT +#ifdef SQL_ATTR_ASYNC_DBC_EVENT case SQL_ATTR_ASYNC_DBC_EVENT: throw DriverException("Optional feature not supported.", "HYC00"); #endif @@ -471,7 +436,7 @@ void ODBCConnection::SetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, case SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE: throw DriverException("Optional feature not supported.", "HYC00"); #endif -#ifdef SQL_ATTR_ASYNC_PCALLBACK +#ifdef SQL_ATTR_ASYNC_DBC_PCALLBACK case SQL_ATTR_ASYNC_DBC_PCALLBACK: throw DriverException("Optional feature not supported.", "HYC00"); #endif @@ -499,7 +464,7 @@ void ODBCConnection::SetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, throw DriverException("Cannot set read-only attribute", "HY092"); case SQL_ATTR_TRACE: // DM-only throw DriverException("Cannot set read-only attribute", "HY092"); - case SQL_ATTR_TRACEFILE: + case SQL_ATTR_TRACEFILE: // DM-only throw DriverException("Optional feature not supported.", "HYC00"); case SQL_ATTR_TRANSLATE_LIB: throw DriverException("Optional feature not supported.", "HYC00"); @@ -573,59 +538,59 @@ void ODBCConnection::SetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, } } -void ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, - SQLINTEGER bufferLength, SQLINTEGER* outputLength, - bool isUnicode) { +SQLRETURN ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, + SQLINTEGER bufferLength, + SQLINTEGER* outputLength, bool isUnicode) { using driver::odbcabstraction::Connection; boost::optional spiAttribute; switch (attribute) { // Internal connection attributes -#ifdef SQL_ATR_ASYNC_DBC_EVENT +#ifdef SQL_ATTR_ASYNC_DBC_EVENT case SQL_ATTR_ASYNC_DBC_EVENT: GetAttribute(static_cast(NULL), value, bufferLength, outputLength); - return; + return SQL_SUCCESS; #endif #ifdef SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE case SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE: GetAttribute(static_cast(SQL_ASYNC_DBC_ENABLE_OFF), value, bufferLength, outputLength); - return; + return SQL_SUCCESS; #endif -#ifdef SQL_ATTR_ASYNC_PCALLBACK +#ifdef SQL_ATTR_ASYNC_DBC_PCALLBACK case SQL_ATTR_ASYNC_DBC_PCALLBACK: GetAttribute(static_cast(NULL), value, bufferLength, outputLength); - return; + return SQL_SUCCESS; #endif #ifdef SQL_ATTR_ASYNC_DBC_PCONTEXT case SQL_ATTR_ASYNC_DBC_PCONTEXT: GetAttribute(static_cast(NULL), value, bufferLength, outputLength); - return; + return SQL_SUCCESS; #endif case SQL_ATTR_ASYNC_ENABLE: GetAttribute(static_cast(SQL_ASYNC_ENABLE_OFF), value, bufferLength, outputLength); - return; + return SQL_SUCCESS; case SQL_ATTR_AUTO_IPD: GetAttribute(static_cast(SQL_FALSE), value, bufferLength, outputLength); - return; + return SQL_SUCCESS; case SQL_ATTR_AUTOCOMMIT: GetAttribute(static_cast(SQL_AUTOCOMMIT_ON), value, bufferLength, outputLength); - return; + return SQL_SUCCESS; #ifdef SQL_ATTR_DBC_INFO_TOKEN case SQL_ATTR_DBC_INFO_TOKEN: throw DriverException("Cannot read set-only attribute", "HY092"); #endif case SQL_ATTR_ENLIST_IN_DTC: GetAttribute(static_cast(NULL), value, bufferLength, outputLength); - return; + return SQL_SUCCESS; case SQL_ATTR_ODBC_CURSORS: // DM-only. throw DriverException("Invalid attribute", "HY092"); case SQL_ATTR_QUIET_MODE: GetAttribute(static_cast(NULL), value, bufferLength, outputLength); - return; + return SQL_SUCCESS; case SQL_ATTR_TRACE: // DM-only throw DriverException("Invalid attribute", "HY092"); case SQL_ATTR_TRACEFILE: @@ -635,7 +600,7 @@ void ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, case SQL_ATTR_TRANSLATE_OPTION: throw DriverException("Optional feature not supported.", "HYC00"); case SQL_ATTR_TXN_ISOLATION: - throw DriverException("Optional feature not supported.", "HCY00"); + throw DriverException("Optional feature not supported.", "HYC00"); // ODBCAbstraction-level connection attributes. case SQL_ATTR_CURRENT_CATALOG: { @@ -644,9 +609,8 @@ void ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, throw DriverException("Optional feature not supported.", "HYC00"); } const std::string& infoValue = boost::get(*catalog); - GetStringAttribute(isUnicode, infoValue, true, value, bufferLength, outputLength, - GetDiagnostics()); - return; + return GetStringAttribute(isUnicode, infoValue, true, value, bufferLength, + outputLength, GetDiagnostics()); } // These all are uint32_t attributes. @@ -675,6 +639,7 @@ void ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, GetAttribute(static_cast(boost::get(*spiAttribute)), value, bufferLength, outputLength); + return SQL_SUCCESS; } void ODBCConnection::disconnect() { @@ -761,7 +726,6 @@ std::string ODBCConnection::getPropertiesFromConnString( if (!isDsnFirst) { isDriverFirst = true; } - continue; } // Strip wrapping curly braces. diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_descriptor.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_descriptor.cc index b578bea36095..97b31bb550e6 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_descriptor.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_descriptor.cc @@ -275,7 +275,9 @@ void ODBCDescriptor::GetHeaderField(SQLSMALLINT fieldIdentifier, SQLPOINTER valu GetAttribute(m_rowsProccessedPtr, value, bufferLength, outputLength); break; case SQL_DESC_COUNT: { - GetAttribute(m_highestOneBasedBoundRecord, value, bufferLength, outputLength); + // m_highestOneBasedBoundRecord equals number of records + 1 + GetAttribute(static_cast(m_highestOneBasedBoundRecord - 1), value, + bufferLength, outputLength); break; } default: @@ -311,52 +313,53 @@ void ODBCDescriptor::GetField(SQLSMALLINT recordNumber, SQLSMALLINT fieldIdentif // TODO: Restrict fields based on AppDescriptor IPD, and IRD. + bool lengthInBytes = true; SQLSMALLINT zeroBasedRecord = recordNumber - 1; const DescriptorRecord& record = m_records[zeroBasedRecord]; switch (fieldIdentifier) { case SQL_DESC_BASE_COLUMN_NAME: - GetAttributeUTF8(record.m_baseColumnName, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_baseColumnName, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_BASE_TABLE_NAME: - GetAttributeUTF8(record.m_baseTableName, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_baseTableName, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_CATALOG_NAME: - GetAttributeUTF8(record.m_catalogName, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_catalogName, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_LABEL: - GetAttributeUTF8(record.m_label, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_label, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_LITERAL_PREFIX: - GetAttributeUTF8(record.m_literalPrefix, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_literalPrefix, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_LITERAL_SUFFIX: - GetAttributeUTF8(record.m_literalSuffix, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_literalSuffix, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_LOCAL_TYPE_NAME: - GetAttributeUTF8(record.m_localTypeName, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_localTypeName, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_NAME: - GetAttributeUTF8(record.m_name, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_name, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_SCHEMA_NAME: - GetAttributeUTF8(record.m_schemaName, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_schemaName, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_TABLE_NAME: - GetAttributeUTF8(record.m_tableName, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_tableName, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_TYPE_NAME: - GetAttributeUTF8(record.m_typeName, value, bufferLength, outputLength, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.m_typeName, lengthInBytes, value, bufferLength, + outputLength, GetDiagnostics()); break; case SQL_DESC_DATA_PTR: @@ -366,7 +369,7 @@ void ODBCDescriptor::GetField(SQLSMALLINT recordNumber, SQLSMALLINT fieldIdentif case SQL_DESC_OCTET_LENGTH_PTR: GetAttribute(record.m_indicatorPtr, value, bufferLength, outputLength); break; - + case SQL_COLUMN_LENGTH: // ODBC 2.0 case SQL_DESC_LENGTH: GetAttribute(record.m_length, value, bufferLength, outputLength); break; @@ -405,12 +408,14 @@ void ODBCDescriptor::GetField(SQLSMALLINT recordNumber, SQLSMALLINT fieldIdentif case SQL_DESC_PARAMETER_TYPE: GetAttribute(record.m_paramType, value, bufferLength, outputLength); break; + case SQL_COLUMN_PRECISION: // ODBC 2.0 case SQL_DESC_PRECISION: GetAttribute(record.m_precision, value, bufferLength, outputLength); break; case SQL_DESC_ROWVER: GetAttribute(record.m_rowVer, value, bufferLength, outputLength); break; + case SQL_COLUMN_SCALE: // ODBC 2.0 case SQL_DESC_SCALE: GetAttribute(record.m_scale, value, bufferLength, outputLength); break; @@ -500,7 +505,8 @@ void ODBCDescriptor::PopulateFromResultSetMetadata(ResultSetMetadata* rsmd) { m_records[i].m_caseSensitive = rsmd->IsCaseSensitive(oneBasedIndex) ? SQL_TRUE : SQL_FALSE; m_records[i].m_datetimeIntervalPrecision; // TODO - update when rsmd adds this - m_records[i].m_numPrecRadix = rsmd->GetNumPrecRadix(oneBasedIndex); + SQLINTEGER numPrecRadix = rsmd->GetNumPrecRadix(oneBasedIndex); + m_records[i].m_numPrecRadix = numPrecRadix > 0 ? numPrecRadix : 0; m_records[i].m_datetimeIntervalCode; // TODO m_records[i].m_fixedPrecScale = rsmd->IsFixedPrecScale(oneBasedIndex) ? SQL_TRUE : SQL_FALSE; @@ -510,8 +516,7 @@ void ODBCDescriptor::PopulateFromResultSetMetadata(ResultSetMetadata* rsmd) { m_records[i].m_rowVer = SQL_FALSE; m_records[i].m_scale = rsmd->GetScale(oneBasedIndex); m_records[i].m_searchable = rsmd->IsSearchable(oneBasedIndex); - m_records[i].m_type = - GetSqlTypeForODBCVersion(rsmd->GetDataType(oneBasedIndex), m_is2xConnection); + m_records[i].m_type = rsmd->GetDataType(oneBasedIndex); m_records[i].m_unnamed = m_records[i].m_name.empty() ? SQL_TRUE : SQL_FALSE; m_records[i].m_unsigned = rsmd->IsUnsigned(oneBasedIndex) ? SQL_TRUE : SQL_FALSE; m_records[i].m_updatable = rsmd->GetUpdatable(oneBasedIndex); diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_environment.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_environment.cc index 7781235688fd..9d7a8223591c 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_environment.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_environment.cc @@ -15,12 +15,13 @@ // specific language governing permissions and limitations // under the License. -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_environment.h" + +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/connection.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/driver.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" -#include -#include -#include -#include #include #include #include diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc index a5db0cc25dde..c990f766df5f 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc @@ -15,16 +15,17 @@ // specific language governing permissions and limitations // under the License. -#include - -#include -#include -#include -#include -#include -#include -#include -#include +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h" + +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set_metadata.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/statement.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h" + #include #include #include @@ -128,6 +129,9 @@ SQLSMALLINT getCTypeForSQLType(const DescriptorRecord& record) { case SQL_WLONGVARCHAR: return SQL_C_WCHAR; + case SQL_BIT: + return SQL_C_BIT; + case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: @@ -145,13 +149,20 @@ SQLSMALLINT getCTypeForSQLType(const DescriptorRecord& record) { case SQL_BIGINT: return record.m_unsigned ? SQL_C_UBIGINT : SQL_C_SBIGINT; + case SQL_NUMERIC: + case SQL_DECIMAL: + return SQL_C_NUMERIC; + + case SQL_FLOAT: case SQL_REAL: return SQL_C_FLOAT; - case SQL_FLOAT: case SQL_DOUBLE: return SQL_C_DOUBLE; + case SQL_GUID: + return SQL_C_GUID; + case SQL_DATE: case SQL_TYPE_DATE: return SQL_C_TYPE_DATE; @@ -306,7 +317,8 @@ void ODBCStatement::ExecuteDirect(const std::string& query) { m_isPrepared = false; } -bool ODBCStatement::Fetch(size_t rows) { +bool ODBCStatement::Fetch(size_t rows, SQLULEN* rowCountPtr, + SQLUSMALLINT* rowStatusArray) { if (m_hasReachedEndOfResult) { m_ird->SetRowsProcessed(0); return false; @@ -339,11 +351,24 @@ bool ODBCStatement::Fetch(size_t rows) { m_currentArd->NotifyBindingsHavePropagated(); } - size_t rowsFetched = m_currenResult->Move(rows, m_currentArd->GetBindOffset(), - m_currentArd->GetBoundStructOffset(), - m_ird->GetArrayStatusPtr()); + uint16_t* arrayStatusPtr; + if (rowStatusArray) { + // For SQLExtendedFetch only + arrayStatusPtr = rowStatusArray; + } else { + arrayStatusPtr = m_ird->GetArrayStatusPtr(); + } + + size_t rowsFetched = + m_currenResult->Move(rows, m_currentArd->GetBindOffset(), + m_currentArd->GetBoundStructOffset(), arrayStatusPtr); m_ird->SetRowsProcessed(static_cast(rowsFetched)); + if (rowCountPtr) { + // For SQLExtendedFetch only + *rowCountPtr = rowsFetched; + } + m_rowNumber += rowsFetched; m_hasReachedEndOfResult = rowsFetched != rows; return rowsFetched != 0; @@ -580,6 +605,7 @@ void ODBCStatement::SetStmtAttr(SQLINTEGER statementAttribute, SQLPOINTER value, return; case SQL_ATTR_ASYNC_ENABLE: + throw DriverException("Unsupported attribute", "HYC00"); #ifdef SQL_ATTR_ASYNC_STMT_EVENT case SQL_ATTR_ASYNC_STMT_EVENT: throw DriverException("Unsupported attribute", "HYC00"); @@ -627,7 +653,7 @@ void ODBCStatement::SetStmtAttr(SQLINTEGER statementAttribute, SQLPOINTER value, CheckIfAttributeIsSetToOnlyValidValue(value, static_cast(SQL_UB_OFF)); return; case SQL_ATTR_RETRIEVE_DATA: - CheckIfAttributeIsSetToOnlyValidValue(value, static_cast(SQL_TRUE)); + CheckIfAttributeIsSetToOnlyValidValue(value, static_cast(SQL_RD_ON)); return; case SQL_ROWSET_SIZE: SetAttribute(value, m_rowsetSize); @@ -677,7 +703,7 @@ void ODBCStatement::RevertAppDescriptor(bool isApd) { void ODBCStatement::closeCursor(bool suppressErrors) { if (!suppressErrors && !m_currenResult) { - throw DriverException("Invalid cursor state", "28000"); + throw DriverException("Invalid cursor state", "24000"); } if (m_currenResult) { @@ -691,9 +717,9 @@ void ODBCStatement::closeCursor(bool suppressErrors) { m_hasReachedEndOfResult = false; } -bool ODBCStatement::GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, - SQLPOINTER dataPtr, SQLLEN bufferLength, - SQLLEN* indicatorPtr) { +SQLRETURN ODBCStatement::GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, + SQLPOINTER dataPtr, SQLLEN bufferLength, + SQLLEN* indicatorPtr) { if (recordNumber == 0) { throw DriverException("Bookmarks are not supported", "07009"); } else if (recordNumber > m_ird->GetRecords().size()) { @@ -735,6 +761,34 @@ bool ODBCStatement::GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType, bufferLength, indicatorPtr); } +SQLRETURN ODBCStatement::getMoreResults() { + // Multiple result sets are not supported. + if (m_currenResult) { + return SQL_NO_DATA; + } else { + throw DriverException("Function sequence error", "HY010"); + } +} + +void ODBCStatement::getColumnCount(SQLSMALLINT* columnCountPtr) { + if (!columnCountPtr) { + // columnCountPtr is not valid, do nothing as ODBC spec does not mention this as an + // error + return; + } + size_t columnCount = m_ird->GetRecords().size(); + *columnCountPtr = static_cast(columnCount); +} + +void ODBCStatement::getRowCount(SQLLEN* rowCountPtr) { + if (!rowCountPtr) { + // rowCountPtr is not valid, do nothing as ODBC spec does not mention this as an error + return; + } + // Will always be -1 (number of rows unknown) if only SELECT is supported + *rowCountPtr = -1; +} + void ODBCStatement::releaseStatement() { closeCursor(true); m_connection.dropStatement(this); diff --git a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/utils.cc b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/utils.cc index f1d2d14744d3..6feb7ff3be20 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbcabstraction/utils.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbcabstraction/utils.cc @@ -42,7 +42,7 @@ boost::optional AsBool(const std::string& value) { boost::optional AsBool(const Connection::ConnPropertyMap& connPropertyMap, const std::string_view& property_name) { - auto extracted_property = connPropertyMap.find(property_name); + auto extracted_property = connPropertyMap.find(std::string(property_name)); if (extracted_property != connPropertyMap.end()) { return AsBool(extracted_property->second); @@ -54,7 +54,7 @@ boost::optional AsBool(const Connection::ConnPropertyMap& connPropertyMap, boost::optional AsInt32(int32_t min_value, const Connection::ConnPropertyMap& connPropertyMap, const std::string_view& property_name) { - auto extracted_property = connPropertyMap.find(property_name); + auto extracted_property = connPropertyMap.find(std::string(property_name)); if (extracted_property != connPropertyMap.end()) { const int32_t stringColumnLength = std::stoi(extracted_property->second); @@ -81,9 +81,8 @@ std::string GetModulePath() { return std::string(path.begin(), path.begin() + dirname_length); } -void ReadConfigFile(PropertyMap& properties, const std::string& config_file_name) { - auto config_path = GetModulePath(); - +void ReadConfigFile(PropertyMap& properties, const std::string& config_path, + const std::string& config_file_name) { std::ifstream config_file; auto config_file_path = config_path + "/" + config_file_name; config_file.open(config_file_path); diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt new file mode 100644 index 000000000000..d5662d088ca2 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt @@ -0,0 +1,53 @@ +# 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. + +add_custom_target(tests) + +include_directories(${ODBC_INCLUDE_DIRS}) + +find_package(SQLite3Alt REQUIRED) + +set(ARROW_FLIGHT_SQL_MOCK_SERVER_SRCS + ../../example/sqlite_sql_info.cc + ../../example/sqlite_type_info.cc + ../../example/sqlite_statement.cc + ../../example/sqlite_statement_batch_reader.cc + ../../example/sqlite_server.cc + ../../example/sqlite_tables_schema_batch_reader.cc) + +add_arrow_test(flight_sql_odbc_test + SOURCES + columns_test.cc + connection_attr_test.cc + connection_info_test.cc + statement_attr_test.cc + statement_test.cc + tables_test.cc + # Connection test needs to be put last to resolve segfault issue + connection_test.cc + odbc_test_suite.cc + odbc_test_suite.h + # Enable Protobuf cleanup after test execution + # GH-46889: move protobuf_test_util to a more common location + ../../../../engine/substrait/protobuf_test_util.cc + ${ARROW_FLIGHT_SQL_MOCK_SERVER_SRCS} + EXTRA_LINK_LIBS + ${ODBC_LIBRARIES} + ${ODBCINST} + ${SQLite3_LIBRARIES} + arrow_odbc_spi_impl + odbcabstraction) diff --git a/cpp/src/arrow/flight/sql/odbc/tests/README b/cpp/src/arrow/flight/sql/odbc/tests/README new file mode 100644 index 000000000000..8e43296edff6 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/README @@ -0,0 +1,4 @@ +Prior to running the tests, set environment variable `ARROW_FLIGHT_SQL_ODBC_CONN` +to a valid connection string. +A valid connection string looks like: +driver={Apache Arrow Flight SQL ODBC Driver};HOST=localhost;port=32010;pwd=myPassword;uid=myName;useEncryption=false; 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..325e59554ced --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -0,0 +1,2324 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +#include "arrow/flight/sql/odbc/tests/odbc_test_suite.h" + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include + +#include "gtest/gtest.h" + +// TODO: add tests with SQLDescribeCol to check metadata of SQLColumns for ODBC 2 and +// ODBC 3. + +namespace arrow::flight::sql::odbc { +// Helper functions +void checkSQLColumns( + SQLHSTMT stmt, const std::wstring& expectedTable, const std::wstring& expectedColumn, + const SQLINTEGER& expectedDataType, const std::wstring& expectedTypeName, + const SQLINTEGER& expectedColumnSize, const SQLINTEGER& expectedBufferLength, + const SQLSMALLINT& expectedDecimalDigits, const SQLSMALLINT& expectedNumPrecRadix, + const SQLSMALLINT& expectedNullable, const SQLSMALLINT& expectedSqlDataType, + const SQLSMALLINT& expectedDateTimeSub, const SQLINTEGER& expectedOctetCharLength, + const SQLINTEGER& expectedOrdinalPosition, const std::wstring& expectedIsNullable) { + CheckStringColumnW(stmt, 3, expectedTable); // table name + CheckStringColumnW(stmt, 4, expectedColumn); // column name + + CheckIntColumn(stmt, 5, expectedDataType); // data type + + CheckStringColumnW(stmt, 6, expectedTypeName); // type name + + CheckIntColumn(stmt, 7, expectedColumnSize); // column size + CheckIntColumn(stmt, 8, expectedBufferLength); // buffer length + + CheckSmallIntColumn(stmt, 9, expectedDecimalDigits); // decimal digits + CheckSmallIntColumn(stmt, 10, expectedNumPrecRadix); // num prec radix + CheckSmallIntColumn(stmt, 11, + expectedNullable); // nullable + + CheckNullColumnW(stmt, 12); // remarks + CheckNullColumnW(stmt, 13); // column def + + CheckSmallIntColumn(stmt, 14, expectedSqlDataType); // sql data type + CheckSmallIntColumn(stmt, 15, expectedDateTimeSub); // sql date type sub + CheckIntColumn(stmt, 16, expectedOctetCharLength); // char octet length + CheckIntColumn(stmt, 17, + expectedOrdinalPosition); // oridinal position + + CheckStringColumnW(stmt, 18, expectedIsNullable); // is nullable +} + +void checkMockSQLColumns( + SQLHSTMT stmt, const std::wstring& expectedCatalog, const std::wstring& expectedTable, + const std::wstring& expectedColumn, const SQLINTEGER& expectedDataType, + const std::wstring& expectedTypeName, const SQLINTEGER& expectedColumnSize, + const SQLINTEGER& expectedBufferLength, const SQLSMALLINT& expectedDecimalDigits, + const SQLSMALLINT& expectedNumPrecRadix, const SQLSMALLINT& expectedNullable, + const SQLSMALLINT& expectedSqlDataType, const SQLSMALLINT& expectedDateTimeSub, + const SQLINTEGER& expectedOctetCharLength, const SQLINTEGER& expectedOrdinalPosition, + const std::wstring& expectedIsNullable) { + CheckStringColumnW(stmt, 1, expectedCatalog); // catalog + CheckNullColumnW(stmt, 2); // schema + + checkSQLColumns(stmt, expectedTable, expectedColumn, expectedDataType, expectedTypeName, + expectedColumnSize, expectedBufferLength, expectedDecimalDigits, + expectedNumPrecRadix, expectedNullable, expectedSqlDataType, + expectedDateTimeSub, expectedOctetCharLength, expectedOrdinalPosition, + expectedIsNullable); +} + +void checkRemoteSQLColumns( + SQLHSTMT stmt, const std::wstring& expectedSchema, const std::wstring& expectedTable, + const std::wstring& expectedColumn, const SQLINTEGER& expectedDataType, + const std::wstring& expectedTypeName, const SQLINTEGER& expectedColumnSize, + const SQLINTEGER& expectedBufferLength, const SQLSMALLINT& expectedDecimalDigits, + const SQLSMALLINT& expectedNumPrecRadix, const SQLSMALLINT& expectedNullable, + const SQLSMALLINT& expectedSqlDataType, const SQLSMALLINT& expectedDateTimeSub, + const SQLINTEGER& expectedOctetCharLength, const SQLINTEGER& expectedOrdinalPosition, + const std::wstring& expectedIsNullable) { + CheckNullColumnW(stmt, 1); // catalog + CheckStringColumnW(stmt, 2, expectedSchema); // schema + checkSQLColumns(stmt, expectedTable, expectedColumn, expectedDataType, expectedTypeName, + expectedColumnSize, expectedBufferLength, expectedDecimalDigits, + expectedNumPrecRadix, expectedNullable, expectedSqlDataType, + expectedDateTimeSub, expectedOctetCharLength, expectedOrdinalPosition, + expectedIsNullable); +} + +void checkSQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT idx, + const std::wstring& expectedColmnName, SQLLEN expectedDataType, + SQLLEN expectedConciseType, SQLLEN expectedDisplaySize, + SQLLEN expectedPrecScale, SQLLEN expectedLength, + const std::wstring& expectedLiteralPrefix, + const std::wstring& expectedLiteralSuffix, + SQLLEN expectedColumnSize, SQLLEN expectedColumnScale, + SQLLEN expectedColumnNullability, SQLLEN expectedNumPrecRadix, + SQLLEN expectedOctetLength, SQLLEN expectedSearchable, + SQLLEN expectedUnsignedColumn) { + std::vector name(ODBC_BUFFER_SIZE); + SQLSMALLINT nameLen = 0; + std::vector baseColumnName(ODBC_BUFFER_SIZE); + SQLSMALLINT columnNameLen = 0; + std::vector label(ODBC_BUFFER_SIZE); + SQLSMALLINT labelLen = 0; + std::vector prefix(ODBC_BUFFER_SIZE); + SQLSMALLINT prefixLen = 0; + std::vector suffix(ODBC_BUFFER_SIZE); + SQLSMALLINT suffixLen = 0; + SQLLEN dataType = 0; + SQLLEN conciseType = 0; + SQLLEN displaySize = 0; + SQLLEN precScale = 0; + SQLLEN length = 0; + SQLLEN size = 0; + SQLLEN scale = 0; + SQLLEN nullability = 0; + SQLLEN numPrecRadix = 0; + SQLLEN octetLength = 0; + SQLLEN searchable = 0; + SQLLEN unsignedCol = 0; + + SQLRETURN ret = SQLColAttribute(stmt, idx, SQL_DESC_NAME, &name[0], + (SQLSMALLINT)name.size(), &nameLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_BASE_COLUMN_NAME, &baseColumnName[0], + (SQLSMALLINT)baseColumnName.size(), &columnNameLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_LABEL, &label[0], (SQLSMALLINT)label.size(), + &labelLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_TYPE, 0, 0, 0, &dataType); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_CONCISE_TYPE, 0, 0, 0, &conciseType); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_DISPLAY_SIZE, 0, 0, 0, &displaySize); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_FIXED_PREC_SCALE, 0, 0, 0, &precScale); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_LENGTH, 0, 0, 0, &length); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_LITERAL_PREFIX, &prefix[0], + (SQLSMALLINT)prefix.size(), &prefixLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_LITERAL_SUFFIX, &suffix[0], + (SQLSMALLINT)suffix.size(), &suffixLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_PRECISION, 0, 0, 0, &size); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_SCALE, 0, 0, 0, &scale); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_NULLABLE, 0, 0, 0, &nullability); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_NUM_PREC_RADIX, 0, 0, 0, &numPrecRadix); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_OCTET_LENGTH, 0, 0, 0, &octetLength); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_SEARCHABLE, 0, 0, 0, &searchable); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_DESC_UNSIGNED, 0, 0, 0, &unsignedCol); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring nameStr = ConvertToWString(name, nameLen); + std::wstring baseColumnNameStr = ConvertToWString(baseColumnName, columnNameLen); + std::wstring labelStr = ConvertToWString(label, labelLen); + std::wstring prefixStr = ConvertToWString(prefix, prefixLen); + + // Assume column name, base column name, and label are equivalent in the result set + EXPECT_EQ(nameStr, expectedColmnName); + EXPECT_EQ(baseColumnNameStr, expectedColmnName); + EXPECT_EQ(labelStr, expectedColmnName); + EXPECT_EQ(dataType, expectedDataType); + EXPECT_EQ(conciseType, expectedConciseType); + EXPECT_EQ(displaySize, expectedDisplaySize); + EXPECT_EQ(precScale, expectedPrecScale); + EXPECT_EQ(length, expectedLength); + EXPECT_EQ(prefixStr, expectedLiteralPrefix); + EXPECT_EQ(size, expectedColumnSize); + EXPECT_EQ(scale, expectedColumnScale); + EXPECT_EQ(nullability, expectedColumnNullability); + EXPECT_EQ(numPrecRadix, expectedNumPrecRadix); + EXPECT_EQ(octetLength, expectedOctetLength); + EXPECT_EQ(searchable, expectedSearchable); + EXPECT_EQ(unsignedCol, expectedUnsignedColumn); +} + +void checkSQLColAttributes(SQLHSTMT stmt, SQLUSMALLINT idx, + const std::wstring& expectedColmnName, SQLLEN expectedDataType, + SQLLEN expectedDisplaySize, SQLLEN expectedPrecScale, + SQLLEN expectedLength, SQLLEN expectedColumnSize, + SQLLEN expectedColumnScale, SQLLEN expectedColumnNullability, + SQLLEN expectedSearchable, SQLLEN expectedUnsignedColumn) { + std::vector name(ODBC_BUFFER_SIZE); + SQLSMALLINT nameLen = 0; + std::vector label(ODBC_BUFFER_SIZE); + SQLSMALLINT labelLen = 0; + SQLLEN dataType = 0; + SQLLEN displaySize = 0; + SQLLEN precScale = 0; + SQLLEN length = 0; + SQLLEN size = 0; + SQLLEN scale = 0; + SQLLEN nullability = 0; + SQLLEN searchable = 0; + SQLLEN unsignedCol = 0; + + SQLRETURN ret = SQLColAttributes(stmt, idx, SQL_COLUMN_NAME, &name[0], + (SQLSMALLINT)name.size(), &nameLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_LABEL, &label[0], + (SQLSMALLINT)label.size(), &labelLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_TYPE, 0, 0, 0, &dataType); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_DISPLAY_SIZE, 0, 0, 0, &displaySize); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(stmt, idx, SQL_COLUMN_MONEY, 0, 0, 0, &precScale); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_LENGTH, 0, 0, 0, &length); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_PRECISION, 0, 0, 0, &size); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_SCALE, 0, 0, 0, &scale); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_NULLABLE, 0, 0, 0, &nullability); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_SEARCHABLE, 0, 0, 0, &searchable); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttributes(stmt, idx, SQL_COLUMN_UNSIGNED, 0, 0, 0, &unsignedCol); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring nameStr = ConvertToWString(name, nameLen); + std::wstring labelStr = ConvertToWString(label, labelLen); + + EXPECT_EQ(nameStr, expectedColmnName); + EXPECT_EQ(labelStr, expectedColmnName); + EXPECT_EQ(dataType, expectedDataType); + EXPECT_EQ(displaySize, expectedDisplaySize); + EXPECT_EQ(length, expectedLength); + EXPECT_EQ(size, expectedColumnSize); + EXPECT_EQ(scale, expectedColumnScale); + EXPECT_EQ(nullability, expectedColumnNullability); + EXPECT_EQ(searchable, expectedSearchable); + EXPECT_EQ(unsignedCol, expectedUnsignedColumn); +} + +void checkSQLColAttributeString(SQLHSTMT stmt, const std::wstring& wsql, SQLUSMALLINT idx, + SQLUSMALLINT fieldIdentifier, + const std::wstring& expectedAttrString) { + // Execute query and check SQLColAttribute string attribute + std::vector sql0(wsql.begin(), wsql.end()); + SQLRETURN ret = SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::vector strVal(ODBC_BUFFER_SIZE); + SQLSMALLINT strLen = 0; + + ret = SQLColAttribute(stmt, idx, fieldIdentifier, &strVal[0], + (SQLSMALLINT)strVal.size(), &strLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring attrStr = ConvertToWString(strVal, strLen); + EXPECT_EQ(attrStr, expectedAttrString); +} + +void checkSQLColAttributeNumeric(SQLHSTMT stmt, const std::wstring& wsql, + SQLUSMALLINT idx, SQLUSMALLINT fieldIdentifier, + SQLLEN expectedAttrNumeric) { + // Execute query and check SQLColAttribute numeric attribute + std::vector sql0(wsql.begin(), wsql.end()); + SQLRETURN ret = SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLLEN numVal = 0; + ret = SQLColAttribute(stmt, idx, fieldIdentifier, 0, 0, 0, &numVal); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(numVal, expectedAttrNumeric); +} + +void checkSQLColAttributesString(SQLHSTMT stmt, const std::wstring& wsql, + SQLUSMALLINT idx, SQLUSMALLINT fieldIdentifier, + const std::wstring& expectedAttrString) { + // Execute query and check ODBC 2.0 API SQLColAttributes string attribute + std::vector sql0(wsql.begin(), wsql.end()); + SQLRETURN ret = SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::vector strVal(ODBC_BUFFER_SIZE); + SQLSMALLINT strLen = 0; + + ret = SQLColAttributes(stmt, idx, fieldIdentifier, &strVal[0], + (SQLSMALLINT)strVal.size(), &strLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring attrStr = ConvertToWString(strVal, strLen); + EXPECT_EQ(attrStr, expectedAttrString); +} + +void checkSQLColAttributesNumeric(SQLHSTMT stmt, const std::wstring& wsql, + SQLUSMALLINT idx, SQLUSMALLINT fieldIdentifier, + SQLLEN expectedAttrNumeric) { + // Execute query and check ODBC 2.0 API SQLColAttributes numeric attribute + std::vector sql0(wsql.begin(), wsql.end()); + SQLRETURN ret = SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLLEN numVal = 0; + ret = SQLColAttributes(stmt, idx, fieldIdentifier, 0, 0, 0, &numVal); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(numVal, expectedAttrNumeric); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLColumnsTestInputData) { + this->connect(); + + SQLWCHAR catalogName[] = L""; + SQLWCHAR schemaName[] = L""; + SQLWCHAR tableName[] = L""; + SQLWCHAR columnName[] = L""; + + // All values populated + SQLRETURN ret = SQLColumns(this->stmt, catalogName, sizeof(catalogName), schemaName, + sizeof(schemaName), tableName, sizeof(tableName), columnName, + sizeof(columnName)); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + // Sizes are nulls + ret = + SQLColumns(this->stmt, catalogName, 0, schemaName, 0, tableName, 0, columnName, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + // Values are nulls + ret = SQLColumns(this->stmt, 0, sizeof(catalogName), 0, sizeof(schemaName), 0, + sizeof(tableName), 0, sizeof(columnName)); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + // Close statement cursor to avoid leaving in an invalid state + SQLFreeStmt(this->stmt, SQL_CLOSE); + + // All values and sizes are nulls + ret = SQLColumns(this->stmt, 0, 0, 0, 0, 0, 0, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllColumns) { + // Check table pattern and column pattern returns all columns + this->connect(); + + // Attempt to get all columns + SQLWCHAR tablePattern[] = L"%"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // mock limitation: SQLite mock server returns 10 for bigint size when spec indicates + // should be 19 + // DECIMAL_DIGITS should be 0 for bigint type since it is exact + // mock limitation: SQLite mock server returns 10 for bigint decimal digits when spec + // indicates should be 0 + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 2nd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"foreignName"), // expectedColumn + SQL_WVARCHAR, // expectedDataType + std::wstring(L"WVARCHAR"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // varchar(100), the ODBC spec expects 100) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_WVARCHAR, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 3rd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"value"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 4th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 5th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"keyName"), // expectedColumn + SQL_WVARCHAR, // expectedDataType + std::wstring(L"WVARCHAR"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // varchar(100), the ODBC spec expects 100) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_WVARCHAR, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 6th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"value"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 7th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"foreignId"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 4, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsAllTypes) { + // Limitation: Mock server returns incorrect values for column size for some columns. + // For character and binary type columns, the driver calculates buffer length and char + // octet length from column size. + + // Checks filtering table with table name pattern + this->connect(); + this->CreateTableAllDataType(); + + // Attempt to get all columns from AllTypesTable + SQLWCHAR tablePattern[] = L"AllTypesTable"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Fetch SQLColumn data for 1st column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"AllTypesTable"), // expectedTable + std::wstring(L"bigint_col"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock server limitation: returns 10, + // the ODBC spec expects 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock server limitation: returns 15, + // the ODBC spec expects 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check SQLColumn data for 2nd column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"AllTypesTable"), // expectedTable + std::wstring(L"char_col"), // expectedColumn + SQL_WVARCHAR, // expectedDataType + std::wstring(L"WVARCHAR"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // varchar(100), the ODBC spec expects 100) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_WVARCHAR, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check SQLColumn data for 3rd column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"AllTypesTable"), // expectedTable + std::wstring(L"varbinary_col"), // expectedColumn + SQL_BINARY, // expectedDataType + std::wstring(L"BINARY"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // BLOB column, spec expects binary data limit) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BINARY, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check SQLColumn data for 4th column in AllTypesTable + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"AllTypesTable"), // expectedTable + std::wstring(L"double_col"), // expectedColumn + SQL_DOUBLE, // expectedDataType + std::wstring(L"DOUBLE"), // expectedTypeName + 15, // expectedColumnSize + 8, // expectedBufferLength + 15, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DOUBLE, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 4, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There should be no more column data + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsUnicode) { + // Limitation: Mock server returns incorrect values for column size for some columns. + // For character and binary type columns, the driver calculates buffer length and char + // octet length from column size. + this->connect(); + this->CreateUnicodeTable(); + + // Attempt to get all columns + SQLWCHAR tablePattern[] = L"数据"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check SQLColumn data for 1st column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"数据"), // expectedTable + std::wstring(L"资料"), // expectedColumn + SQL_WVARCHAR, // expectedDataType + std::wstring(L"WVARCHAR"), // expectedTypeName + 0, // expectedColumnSize (mock server limitation: returns 0 for + // varchar(100), spec expects 100) + 0, // expectedBufferLength + 15, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_WVARCHAR, // expectedSqlDataType + NULL, // expectedDateTimeSub + 0, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There should be no more column data + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColumnsAllTypes) { + // GH-47159: Return NUM_PREC_RADIX based on whether COLUMN_SIZE contains number of + // digits or bits + this->connect(); + + SQLWCHAR tablePattern[] = L"ODBCTest"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check 1st Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"sinteger_max"), // expectedColumn + SQL_INTEGER, // expectedDataType + std::wstring(L"INTEGER"), // expectedTypeName + 32, // expectedColumnSize (remote server returns number of bits) + 4, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_INTEGER, // expectedSqlDataType + NULL, // expectedDateTimeSub + 4, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 2nd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"sbigint_max"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 64, // expectedColumnSize (remote server returns number of bits) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 3rd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"decimal_positive"), // expectedColumn + SQL_DECIMAL, // expectedDataType + std::wstring(L"DECIMAL"), // expectedTypeName + 38, // expectedColumnSize + 19, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DECIMAL, // expectedSqlDataType + NULL, // expectedDateTimeSub + 2, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 4th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"float_max"), // expectedColumn + SQL_FLOAT, // expectedDataType + std::wstring(L"FLOAT"), // expectedTypeName + 24, // expectedColumnSize (precision bits from IEEE 754) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_FLOAT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 4, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 5th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"double_max"), // expectedColumn + SQL_DOUBLE, // expectedDataType + std::wstring(L"DOUBLE"), // expectedTypeName + 53, // expectedColumnSize (precision bits from IEEE 754) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DOUBLE, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 5, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 6th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"bit_true"), // expectedColumn + SQL_BIT, // expectedDataType + std::wstring(L"BOOLEAN"), // expectedTypeName + 0, // expectedColumnSize (limitation: remote server remote server + // returns 0, should be 1) + 1, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 1, // expectedOctetCharLength + 6, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // ODBC ver 3 returns SQL_TYPE_DATE, SQL_TYPE_TIME, and SQL_TYPE_TIMESTAMP in the + // DATA_TYPE field + + // Check 7th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"date_max"), // expectedColumn + SQL_TYPE_DATE, // expectedDataType + std::wstring(L"DATE"), // expectedTypeName + 0, // expectedColumnSize (limitation: remote server returns 0, should be 10) + 10, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_DATE, // expectedDateTimeSub + 6, // expectedOctetCharLength + 7, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 8th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"time_max"), // expectedColumn + SQL_TYPE_TIME, // expectedDataType + std::wstring(L"TIME"), // expectedTypeName + 3, // expectedColumnSize (limitation: should be 9+fractional digits) + 12, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_TIME, // expectedDateTimeSub + 6, // expectedOctetCharLength + 8, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 9th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"timestamp_max"), // expectedColumn + SQL_TYPE_TIMESTAMP, // expectedDataType + std::wstring(L"TIMESTAMP"), // expectedTypeName + 3, // expectedColumnSize (limitation: should be 20+fractional digits) + 23, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_TIMESTAMP, // expectedDateTimeSub + 16, // expectedOctetCharLength + 9, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There is no more column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColumnsAllTypesODBCVer2) { + // GH-47159: Return NUM_PREC_RADIX based on whether COLUMN_SIZE contains number of + // digits or bits + this->connect(SQL_OV_ODBC2); + + SQLWCHAR tablePattern[] = L"ODBCTest"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check 1st Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"sinteger_max"), // expectedColumn + SQL_INTEGER, // expectedDataType + std::wstring(L"INTEGER"), // expectedTypeName + 32, // expectedColumnSize (remote server returns number of bits) + 4, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_INTEGER, // expectedSqlDataType + NULL, // expectedDateTimeSub + 4, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 2nd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"sbigint_max"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 64, // expectedColumnSize (remote server returns number of bits) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 2, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 3rd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"decimal_positive"), // expectedColumn + SQL_DECIMAL, // expectedDataType + std::wstring(L"DECIMAL"), // expectedTypeName + 38, // expectedColumnSize + 19, // expectedBufferLength + 0, // expectedDecimalDigits + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DECIMAL, // expectedSqlDataType + NULL, // expectedDateTimeSub + 2, // expectedOctetCharLength + 3, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 4th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"float_max"), // expectedColumn + SQL_FLOAT, // expectedDataType + std::wstring(L"FLOAT"), // expectedTypeName + 24, // expectedColumnSize (precision bits from IEEE 754) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_FLOAT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 4, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 5th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"double_max"), // expectedColumn + SQL_DOUBLE, // expectedDataType + std::wstring(L"DOUBLE"), // expectedTypeName + 53, // expectedColumnSize (precision bits from IEEE 754) + 8, // expectedBufferLength + 0, // expectedDecimalDigits + 2, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DOUBLE, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 5, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 6th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns(this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"bit_true"), // expectedColumn + SQL_BIT, // expectedDataType + std::wstring(L"BOOLEAN"), // expectedTypeName + 0, // expectedColumnSize (limitation: remote server remote server + // returns 0, should be 1) + 1, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 1, // expectedOctetCharLength + 6, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // ODBC ver 2 returns SQL_DATE, SQL_TIME, and SQL_TIMESTAMP in the DATA_TYPE field + + // Check 7th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"date_max"), // expectedColumn + SQL_DATE, // expectedDataType + std::wstring(L"DATE"), // expectedTypeName + 0, // expectedColumnSize (limitation: remote server returns 0, should be 10) + 10, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_DATE, // expectedDateTimeSub + 6, // expectedOctetCharLength + 7, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 8th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"time_max"), // expectedColumn + SQL_TIME, // expectedDataType + std::wstring(L"TIME"), // expectedTypeName + 3, // expectedColumnSize (limitation: should be 9+fractional digits) + 12, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_TIME, // expectedDateTimeSub + 6, // expectedOctetCharLength + 8, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 9th Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkRemoteSQLColumns( + this->stmt, + std::wstring(L"$scratch"), // expectedSchema + std::wstring(L"ODBCTest"), // expectedTable + std::wstring(L"timestamp_max"), // expectedColumn + SQL_TIMESTAMP, // expectedDataType + std::wstring(L"TIMESTAMP"), // expectedTypeName + 3, // expectedColumnSize (limitation: should be 20+fractional digits) + 23, // expectedBufferLength + 0, // expectedDecimalDigits + 0, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_DATETIME, // expectedSqlDataType + SQL_CODE_TIMESTAMP, // expectedDateTimeSub + 16, // expectedOctetCharLength + 9, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There is no more column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsColumnPattern) { + // Checks filtering table with column name pattern. + // Only check table and column name + this->connect(); + + SQLWCHAR tablePattern[] = L"%"; + SQLWCHAR columnPattern[] = L"id"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check 1st Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // Check 2nd Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"intTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There is no more column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsTableColumnPattern) { + // Checks filtering table with table and column name pattern. + // Only check table and column name + this->connect(); + + SQLWCHAR tablePattern[] = L"foreignTable"; + SQLWCHAR columnPattern[] = L"id"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check 1st Column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkMockSQLColumns(this->stmt, + std::wstring(L"main"), // expectedCatalog + std::wstring(L"foreignTable"), // expectedTable + std::wstring(L"id"), // expectedColumn + SQL_BIGINT, // expectedDataType + std::wstring(L"BIGINT"), // expectedTypeName + 10, // expectedColumnSize (mock returns 10 instead of 19) + 8, // expectedBufferLength + 15, // expectedDecimalDigits (mock returns 15 instead of 0) + 10, // expectedNumPrecRadix + SQL_NULLABLE, // expectedNullable + SQL_BIGINT, // expectedSqlDataType + NULL, // expectedDateTimeSub + 8, // expectedOctetCharLength + 1, // expectedOrdinalPosition + std::wstring(L"YES")); // expectedIsNullable + + // There is no more column + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColumnsInvalidTablePattern) { + this->connect(); + + SQLWCHAR tablePattern[] = L"non-existent-table"; + SQLWCHAR columnPattern[] = L"%"; + + SQLRETURN ret = SQLColumns(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, tablePattern, + SQL_NTS, columnPattern, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // There is no column from filter + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLColAttributeTestInputData) { + this->connect(); + + std::wstring wsql = L"SELECT 1 as col1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLUSMALLINT idx = 1; + std::vector characterAttr(ODBC_BUFFER_SIZE); + SQLSMALLINT characterAttrLen = 0; + SQLLEN numericAttr = 0; + + // All character values populated + ret = SQLColAttribute(this->stmt, idx, SQL_DESC_NAME, &characterAttr[0], + (SQLSMALLINT)characterAttr.size(), &characterAttrLen, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + // All numeric values populated + ret = SQLColAttribute(this->stmt, idx, SQL_DESC_COUNT, 0, 0, 0, &numericAttr); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Pass null values, driver should not throw error + ret = SQLColAttribute(this->stmt, idx, SQL_COLUMN_TABLE_NAME, 0, 0, 0, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLColAttribute(this->stmt, idx, SQL_DESC_COUNT, 0, 0, 0, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLColAttributeGetCharacterLen) { + this->connect(); + + std::wstring wsql = L"SELECT 1 as col1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLSMALLINT characterAttrLen = 0; + + // Check length of character attribute + ret = SQLColAttribute(this->stmt, 1, SQL_DESC_BASE_COLUMN_NAME, 0, 0, &characterAttrLen, + 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(characterAttrLen, 4 * ODBC::GetSqlWCharSize()); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLColAttributeInvalidFieldId) { + this->connect(); + + std::wstring wsql = L"SELECT 1 as col1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLUSMALLINT invalidFieldId = -100; + SQLUSMALLINT idx = 1; + std::vector characterAttr(ODBC_BUFFER_SIZE); + SQLSMALLINT characterAttrLen = 0; + SQLLEN numericAttr = 0; + + ret = SQLColAttribute(this->stmt, idx, invalidFieldId, &characterAttr[0], + (SQLSMALLINT)characterAttr.size(), &characterAttrLen, 0); + EXPECT_EQ(ret, SQL_ERROR); + // Verify invalid descriptor field identifier error state is returned + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY091); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLColAttributeInvalidColId) { + this->connect(); + + std::wstring wsql = L"SELECT 1 as col1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLUSMALLINT invalidColId = 2; + std::vector characterAttr(ODBC_BUFFER_SIZE); + SQLSMALLINT characterAttrLen = 0; + SQLLEN numericAttr = 0; + + ret = SQLColAttribute(this->stmt, invalidColId, SQL_DESC_BASE_COLUMN_NAME, + &characterAttr[0], (SQLSMALLINT)characterAttr.size(), + &characterAttrLen, 0); + EXPECT_EQ(ret, SQL_ERROR); + // Verify invalid descriptor index error state is returned + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_07009); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeAllTypes) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkSQLColAttribute(this->stmt, 1, + std::wstring(L"bigint_col"), // expectedColmnName + SQL_BIGINT, // expectedDataType + SQL_BIGINT, // expectedConciseType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 2, + std::wstring(L"char_col"), // expectedColmnName + SQL_WVARCHAR, // expectedDataType + SQL_WVARCHAR, // expectedConciseType + 0, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 0, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 0, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 0, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 3, + std::wstring(L"varbinary_col"), // expectedColmnName + SQL_BINARY, // expectedDataType + SQL_BINARY, // expectedConciseType + 0, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 0, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 0, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 0, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 4, + std::wstring(L"double_col"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + SQL_DOUBLE, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesAllTypesODBCVer2) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + checkSQLColAttributes(this->stmt, 1, + std::wstring(L"bigint_col"), // expectedColmnName + SQL_BIGINT, // expectedDataType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 2, + std::wstring(L"char_col"), // expectedColmnName + SQL_WVARCHAR, // expectedDataType + 0, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 0, // expectedLength + 0, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 3, + std::wstring(L"varbinary_col"), // expectedColmnName + SQL_BINARY, // expectedDataType + 0, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 0, // expectedLength + 0, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 4, + std::wstring(L"double_col"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeAllTypes) { + // Test assumes there is a table $scratch.ODBCTest in remote server + this->connect(); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkSQLColAttribute(this->stmt, 1, + std::wstring(L"sinteger_max"), // expectedColmnName + SQL_INTEGER, // expectedDataType + SQL_INTEGER, // expectedConciseType + 11, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 4, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 4, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 4, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 2, + std::wstring(L"sbigint_max"), // expectedColmnName + SQL_BIGINT, // expectedDataType + SQL_BIGINT, // expectedConciseType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 3, + std::wstring(L"decimal_positive"), // expectedColmnName + SQL_DECIMAL, // expectedDataType + SQL_DECIMAL, // expectedConciseType + 40, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 19, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 19, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 40, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 4, + std::wstring(L"float_max"), // expectedColmnName + SQL_FLOAT, // expectedDataType + SQL_FLOAT, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 5, + std::wstring(L"double_max"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + SQL_DOUBLE, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 6, + std::wstring(L"bit_true"), // expectedColmnName + SQL_BIT, // expectedDataType + SQL_BIT, // expectedConciseType + 1, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 1, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 1, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 1, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 7, + std::wstring(L"date_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TYPE_DATE, // expectedConciseType + 10, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 10, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 10, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 6, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 8, + std::wstring(L"time_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TYPE_TIME, // expectedConciseType + 12, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 12, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 12, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 6, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 9, + std::wstring(L"timestamp_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TYPE_TIMESTAMP, // expectedConciseType + 23, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 23, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 23, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 16, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeAllTypesODBCVer2) { + // Test assumes there is a table $scratch.ODBCTest in remote server + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkSQLColAttribute(this->stmt, 1, + std::wstring(L"sinteger_max"), // expectedColmnName + SQL_INTEGER, // expectedDataType + SQL_INTEGER, // expectedConciseType + 11, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 4, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 4, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 4, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 2, + std::wstring(L"sbigint_max"), // expectedColmnName + SQL_BIGINT, // expectedDataType + SQL_BIGINT, // expectedConciseType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 3, + std::wstring(L"decimal_positive"), // expectedColmnName + SQL_DECIMAL, // expectedDataType + SQL_DECIMAL, // expectedConciseType + 40, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 19, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 19, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 10, // expectedNumPrecRadix + 40, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 4, + std::wstring(L"float_max"), // expectedColmnName + SQL_FLOAT, // expectedDataType + SQL_FLOAT, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 5, + std::wstring(L"double_max"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + SQL_DOUBLE, // expectedConciseType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 2, // expectedNumPrecRadix + 8, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 6, + std::wstring(L"bit_true"), // expectedColmnName + SQL_BIT, // expectedDataType + SQL_BIT, // expectedConciseType + 1, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 1, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 1, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 1, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 7, + std::wstring(L"date_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_DATE, // expectedConciseType + 10, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 10, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 10, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 6, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 8, + std::wstring(L"time_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TIME, // expectedConciseType + 12, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 12, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 12, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 6, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttribute(this->stmt, 9, + std::wstring(L"timestamp_max"), // expectedColmnName + SQL_DATETIME, // expectedDataType + SQL_TIMESTAMP, // expectedConciseType + 23, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 23, // expectedLength + std::wstring(L""), // expectedLiteralPrefix + std::wstring(L""), // expectedLiteralSuffix + 23, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + 0, // expectedNumPrecRadix + 16, // expectedOctetLength + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributesAllTypesODBCVer2) { + // Tests ODBC 2.0 API SQLColAttributes + // Test assumes there is a table $scratch.ODBCTest in remote server + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + checkSQLColAttributes(this->stmt, 1, + std::wstring(L"sinteger_max"), // expectedColmnName + SQL_INTEGER, // expectedDataType + 11, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 4, // expectedLength + 4, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 2, + std::wstring(L"sbigint_max"), // expectedColmnName + SQL_BIGINT, // expectedDataType + 20, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 3, + std::wstring(L"decimal_positive"), // expectedColmnName + SQL_DECIMAL, // expectedDataType + 40, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 19, // expectedLength + 19, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 4, + std::wstring(L"float_max"), // expectedColmnName + SQL_FLOAT, // expectedDataType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 5, + std::wstring(L"double_max"), // expectedColmnName + SQL_DOUBLE, // expectedDataType + 24, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 8, // expectedLength + 8, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_FALSE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 6, + std::wstring(L"bit_true"), // expectedColmnName + SQL_BIT, // expectedDataType + 1, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 1, // expectedLength + 1, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 7, + std::wstring(L"date_max"), // expectedColmnName + SQL_DATE, // expectedDataType + 10, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 10, // expectedLength + 10, // expectedColumnSize + 0, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 8, + std::wstring(L"time_max"), // expectedColmnName + SQL_TIME, // expectedDataType + 12, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 12, // expectedLength + 12, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + checkSQLColAttributes(this->stmt, 9, + std::wstring(L"timestamp_max"), // expectedColmnName + SQL_TIMESTAMP, // expectedDataType + 23, // expectedDisplaySize + SQL_FALSE, // expectedPrecScale + 23, // expectedLength + 23, // expectedColumnSize + 3, // expectedColumnScale + SQL_NULLABLE, // expectedColumnNullability + SQL_PRED_NONE, // expectedSearchable + SQL_TRUE); // expectedUnsignedColumn + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributeCaseSensitive) { + // Arrow limitation: returns SQL_FALSE for case sensitive column + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Int column + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_CASE_SENSITIVE, SQL_FALSE); + SQLFreeStmt(this->stmt, SQL_CLOSE); + // Varchar column + checkSQLColAttributeNumeric(this->stmt, wsql, 28, SQL_DESC_CASE_SENSITIVE, SQL_FALSE); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributesCaseSensitive) { + // Arrow limitation: returns SQL_FALSE for case sensitive column + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Int column + checkSQLColAttributesNumeric(this->stmt, wsql, 1, SQL_COLUMN_CASE_SENSITIVE, SQL_FALSE); + SQLFreeStmt(this->stmt, SQL_CLOSE); + // Varchar column + checkSQLColAttributesNumeric(this->stmt, wsql, 28, SQL_COLUMN_CASE_SENSITIVE, + SQL_FALSE); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeUniqueValue) { + // Mock server limitation: returns false for auto-increment column + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_AUTO_UNIQUE_VALUE, SQL_FALSE); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesAutoIncrement) { + // Tests ODBC 2.0 API SQLColAttributes + // Mock server limitation: returns false for auto-increment column + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_COLUMN_AUTO_INCREMENT, SQL_FALSE); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeBaseTableName) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_BASE_TABLE_NAME, + std::wstring(L"AllTypesTable")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesTableName) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TABLE_NAME, + std::wstring(L"AllTypesTable")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeCatalogName) { + // Mock server limitattion: mock doesn't return catalog for result metadata, + // and the defautl catalog should be 'main' + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_CATALOG_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeCatalogName) { + // Remote server does not have catalogs + this->connect(); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_CATALOG_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesQualifierName) { + // Mock server limitattion: mock doesn't return catalog for result metadata, + // and the defautl catalog should be 'main' + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_COLUMN_QUALIFIER_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributesQualifierName) { + // Remote server does not have catalogs + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_COLUMN_QUALIFIER_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributeCount) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Pass 0 as column number, driver should ignore it + checkSQLColAttributeNumeric(this->stmt, wsql, 0, SQL_DESC_COUNT, 32); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeLocalTypeName) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Mock server doesn't have local type name + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_LOCAL_TYPE_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeLocalTypeName) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_LOCAL_TYPE_NAME, + std::wstring(L"INTEGER")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeSchemaName) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't have schemas + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_SCHEMA_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeSchemaName) { + // Test assumes there is a table $scratch.ODBCTest in remote server + this->connect(); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + // Remote server limitation: doesn't return schema name, expected schema name is + // $scratch + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_SCHEMA_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesOwnerName) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't have schemas + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_OWNER_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributesOwnerName) { + // Test assumes there is a table $scratch.ODBCTest in remote server + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + // Remote server limitation: doesn't return schema name, expected schema name is + // $scratch + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_OWNER_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeTableName) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TABLE_NAME, + std::wstring(L"AllTypesTable")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributeTypeName) { + this->connect(); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't return data source-dependent data type name + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TYPE_NAME, std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributeTypeName) { + this->connect(); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + checkSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TYPE_NAME, + std::wstring(L"INTEGER")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLColAttributesTypeName) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't return data source-dependent data type name + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TYPE_NAME, + std::wstring(L"")); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLColAttributesTypeName) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + checkSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TYPE_NAME, + std::wstring(L"INTEGER")); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributeUnnamed) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_UNNAMED, SQL_NAMED); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributeUpdatable) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Mock server and remote server do not return updatable information + checkSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_UPDATABLE, + SQL_ATTR_READWRITE_UNKNOWN); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLColAttributesUpdatable) { + // Tests ODBC 2.0 API SQLColAttributes + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = this->getQueryAllDataTypes(); + // Mock server and remote server do not return updatable information + checkSQLColAttributesNumeric(this->stmt, wsql, 1, SQL_COLUMN_UPDATABLE, + SQL_ATTR_READWRITE_UNKNOWN); + + this->disconnect(); +} +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_attr_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/connection_attr_test.cc new file mode 100644 index 000000000000..b725711f56e2 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_attr_test.cc @@ -0,0 +1,565 @@ +// 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 { + +#ifdef SQL_ATTR_ASYNC_DBC_EVENT +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrAsyncDbcEventUnsupported) { + this->connect(); + + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_EVENT, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + // Driver Manager on Windows returns error code HY118 + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY118); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_ENABLE +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrAyncEnableUnsupported) { + this->connect(); + + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_ENABLE, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_DBC_PCALLBACK +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrAyncDbcPcCallbackUnsupported) { + this->connect(); + + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_PCALLBACK, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_DBC_PCONTEXT +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrAyncDbcPcContextUnsupported) { + this->connect(); + + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_PCONTEXT, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} +#endif + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrAutoIpdReadOnly) { + this->connect(); + + // Verify read-only attribute cannot be set + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_AUTO_IPD, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY092); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrConnectionDeadReadOnly) { + this->connect(); + + // Verify read-only attribute cannot be set + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_CONNECTION_DEAD, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY092); + + this->disconnect(); +} + +#ifdef SQL_ATTR_DBC_INFO_TOKEN +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrDbcInfoTokenUnsupported) { + this->connect(); + + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_DBC_INFO_TOKEN, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} +#endif + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrEnlistInDtcUnsupported) { + this->connect(); + + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ENLIST_IN_DTC, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrOdbcCursorsDMOnly) { + this->allocEnvConnHandles(); + + // Verify DM-only attribute is settable via Driver Manager + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ODBC_CURSORS, + reinterpret_cast(SQL_CUR_USE_DRIVER), 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + std::string connect_str = this->getConnectionString(); + this->connectWithString(connect_str); + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrQuietModeReadOnly) { + this->connect(); + + // Verify read-only attribute cannot be set + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_QUIET_MODE, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY092); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrTraceDMOnly) { + this->connect(); + + // Verify DM-only attribute is settable via Driver Manager + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_TRACE, + reinterpret_cast(SQL_OPT_TRACE_OFF), 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrTracefileDMOnly) { + this->connect(); + + // Verify DM-only attribute is handled by Driver Manager + + // Use placeholder value as we want the call to fail, or else + // the driver manager will produce a trace file. + std::wstring trace_file = L"invalid/file/path"; + std::vector trace_file0(trace_file.begin(), trace_file.end()); + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_TRACEFILE, &trace_file0[0], + static_cast(trace_file0.size())); + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY000); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrTranslateLabDMOnly) { + this->connect(); + + // Verify DM-only attribute is handled by Driver Manager + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_TRANSLATE_LIB, 0, 0); + EXPECT_EQ(ret, SQL_ERROR); + // Checks for invalid argument return error + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY024); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrTranslateOptionUnsupported) { + this->connect(); + + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_TRANSLATE_OPTION, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrTxnIsolationUnsupported) { + this->connect(); + + SQLRETURN ret = + SQLSetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, + reinterpret_cast(SQL_TXN_READ_UNCOMMITTED), 0); + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} + +#ifdef SQL_ATTR_DBC_INFO_TOKEN +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrDbcInfoTokenSetOnly) { + this->connect(); + + // Verify that set-only attribute cannot be read + SQLPOINTER ptr = NULL; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_DBC_INFO_TOKEN, ptr, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY092); + + this->disconnect(); +} +#endif + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrOdbcCursorsDMOnly) { + this->connect(); + + // Verify that DM-only attribute is handled by driver manager + SQLULEN cursor_attr; + SQLRETURN ret = + SQLGetConnectAttr(this->conn, SQL_ATTR_ODBC_CURSORS, &cursor_attr, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(cursor_attr, SQL_CUR_USE_DRIVER); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrTraceDMOnly) { + this->connect(); + + // Verify that DM-only attribute is handled by driver manager + SQLUINTEGER trace; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_TRACE, &trace, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(trace, SQL_OPT_TRACE_OFF); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrTraceFileDMOnly) { + this->connect(); + + // Verify that DM-only attribute is handled by driver manager + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLINTEGER outstrlen; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_TRACEFILE, outstr, + ODBC_BUFFER_SIZE, &outstrlen); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Length is returned in bytes for SQLGetConnectAttr, + // we want the number of characters + outstrlen /= driver::odbcabstraction::GetSqlWCharSize(); + std::string out_connection_string = + ODBC::SqlWcharToString(outstr, static_cast(outstrlen)); + EXPECT_TRUE(!out_connection_string.empty()); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrTranslateLibUnsupported) { + this->connect(); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLINTEGER outstrlen; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_TRANSLATE_LIB, outstr, + ODBC_BUFFER_SIZE, &outstrlen); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrTranslateOptionUnsupported) { + this->connect(); + + SQLINTEGER option; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_TRANSLATE_OPTION, &option, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrTxnIsolationUnsupported) { + this->connect(); + + SQLINTEGER isolation; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, &isolation, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HYC00); + + this->disconnect(); +} + +#ifdef SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE +TYPED_TEST(FlightSQLODBCTestBase, + TestSQLGetConnectAttrAsyncDbcFunctionsEnableUnsupported) { + this->connect(); + + // Verifies that the Windows driver manager returns HY114 for unsupported functionality + SQLUINTEGER enable; + SQLRETURN ret = + SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, &enable, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY114); + + this->disconnect(); +} +#endif + +// Tests for supported attributes + +#ifdef SQL_ATTR_ASYNC_DBC_EVENT +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrAsyncDbcEventDefault) { + this->connect(); + + SQLPOINTER ptr = NULL; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_EVENT, ptr, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ptr, reinterpret_cast(NULL)); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_DBC_PCALLBACK +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrAsyncDbcPcallbackDefault) { + this->connect(); + + SQLPOINTER ptr = NULL; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_PCALLBACK, ptr, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ptr, reinterpret_cast(NULL)); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_DBC_PCONTEXT +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrAsyncDbcPcontextDefault) { + this->connect(); + + SQLPOINTER ptr = NULL; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_PCONTEXT, ptr, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ptr, reinterpret_cast(NULL)); + + this->disconnect(); +} +#endif + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrAsyncEnableDefault) { + this->connect(); + + SQLULEN enable; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_ENABLE, &enable, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(enable, SQL_ASYNC_ENABLE_OFF); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrAutoIpdDefault) { + this->connect(); + + SQLUINTEGER ipd; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_AUTO_IPD, &ipd, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ipd, static_cast(SQL_FALSE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrAutocommitDefault) { + this->connect(); + + SQLUINTEGER auto_commit; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_AUTOCOMMIT, &auto_commit, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(auto_commit, SQL_AUTOCOMMIT_ON); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrEnlistInDtcDefault) { + this->connect(); + + SQLPOINTER ptr = NULL; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_ENLIST_IN_DTC, ptr, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ptr, reinterpret_cast(NULL)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetConnectAttrQuietModeDefault) { + this->connect(); + + HWND ptr = NULL; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_QUIET_MODE, ptr, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ptr, reinterpret_cast(NULL)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrAccessModeValid) { + this->connect(); + + // The driver always returns SQL_MODE_READ_WRITE + + // Check default value first + SQLUINTEGER mode = -1; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_ACCESS_MODE, &mode, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(mode, SQL_MODE_READ_WRITE); + + ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ACCESS_MODE, + reinterpret_cast(SQL_MODE_READ_WRITE), 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + mode = -1; + + ret = SQLGetConnectAttr(this->conn, SQL_ATTR_ACCESS_MODE, &mode, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(mode, SQL_MODE_READ_WRITE); + + // Attempt to set to SQL_MODE_READ_ONLY, driver should return warning and not error + ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ACCESS_MODE, + reinterpret_cast(SQL_MODE_READ_ONLY), 0); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + + // Verify warning status + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_01S02); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrConnectionTimeoutValid) { + this->connect(); + + // Check default value first + SQLUINTEGER timeout = -1; + SQLRETURN ret = + SQLGetConnectAttr(this->conn, SQL_ATTR_CONNECTION_TIMEOUT, &timeout, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(timeout, 0); + + ret = SQLSetConnectAttr(this->conn, SQL_ATTR_CONNECTION_TIMEOUT, + reinterpret_cast(42), 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + timeout = -1; + + ret = SQLGetConnectAttr(this->conn, SQL_ATTR_CONNECTION_TIMEOUT, &timeout, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(timeout, 42); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrLoginTimeoutValid) { + this->connect(); + + // Check default value first + SQLUINTEGER timeout = -1; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_LOGIN_TIMEOUT, &timeout, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(timeout, 0); + + ret = SQLSetConnectAttr(this->conn, SQL_ATTR_LOGIN_TIMEOUT, + reinterpret_cast(42), 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + timeout = -1; + + ret = SQLGetConnectAttr(this->conn, SQL_ATTR_LOGIN_TIMEOUT, &timeout, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(timeout, 42); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetConnectAttrPacketSizeValid) { + this->connect(); + + // The driver always returns 0. PACKET_SIZE value is unused by the driver. + + // Check default value first + SQLUINTEGER size = -1; + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_PACKET_SIZE, &size, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(size, 0); + + ret = SQLSetConnectAttr(this->conn, SQL_ATTR_PACKET_SIZE, + reinterpret_cast(0), 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + size = -1; + + ret = SQLGetConnectAttr(this->conn, SQL_ATTR_PACKET_SIZE, &size, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(size, 0); + + // Attempt to set to non-zero value, driver should return warning and not error + ret = SQLSetConnectAttr(this->conn, SQL_ATTR_PACKET_SIZE, + reinterpret_cast(2), 0); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + + // Verify warning status + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_01S02); + + this->disconnect(); +} + +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_info_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/connection_info_test.cc new file mode 100644 index 000000000000..39bf7e1440b1 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_info_test.cc @@ -0,0 +1,1487 @@ +// 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 { + +// Helper Functions + +// Validate unsigned short SQLUSMALLINT return value +void validate(SQLHDBC connection, SQLUSMALLINT infoType, SQLUSMALLINT expected_value) { + SQLUSMALLINT info_value; + SQLSMALLINT message_length; + + SQLRETURN ret = SQLGetInfo(connection, infoType, &info_value, 0, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(info_value, expected_value); +} + +// Validate unsigned long SQLUINTEGER return value +void validate(SQLHDBC connection, SQLUSMALLINT infoType, SQLUINTEGER expected_value) { + SQLUINTEGER info_value; + SQLSMALLINT message_length; + + SQLRETURN ret = SQLGetInfo(connection, infoType, &info_value, 0, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(info_value, expected_value); +} + +// Validate unsigned length SQLULEN return value +void validate(SQLHDBC connection, SQLUSMALLINT infoType, SQLULEN expected_value) { + SQLULEN info_value; + SQLSMALLINT message_length; + + SQLRETURN ret = SQLGetInfo(connection, infoType, &info_value, 0, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(info_value, expected_value); +} + +// Validate wchar string SQLWCHAR return value +void validate(SQLHDBC connection, SQLUSMALLINT infoType, SQLWCHAR* expected_value) { + SQLWCHAR info_value[ODBC_BUFFER_SIZE] = L""; + SQLSMALLINT message_length; + + SQLRETURN ret = + SQLGetInfo(connection, infoType, info_value, ODBC_BUFFER_SIZE, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(*info_value, *expected_value); +} + +// Validate unsigned long SQLUINTEGER return value is greater than +void validateGreaterThan(SQLHDBC connection, SQLUSMALLINT infoType, + SQLUINTEGER compared_value) { + SQLUINTEGER info_value; + SQLSMALLINT message_length; + + SQLRETURN ret = SQLGetInfo(connection, infoType, &info_value, 0, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(info_value, compared_value); +} + +// Validate unsigned length SQLULEN return value is greater than +void validateGreaterThan(SQLHDBC connection, SQLUSMALLINT infoType, + SQLULEN compared_value) { + SQLULEN info_value; + SQLSMALLINT message_length; + + SQLRETURN ret = SQLGetInfo(connection, infoType, &info_value, 0, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(info_value, compared_value); +} + +// Validate wchar string SQLWCHAR return value is not empty +void validateNotEmptySQLWCHAR(SQLHDBC connection, SQLUSMALLINT infoType, + bool allowTruncation) { + SQLWCHAR info_value[ODBC_BUFFER_SIZE] = L""; + SQLSMALLINT message_length; + + SQLRETURN ret = + SQLGetInfo(connection, infoType, info_value, ODBC_BUFFER_SIZE, &message_length); + + if (allowTruncation && ret == SQL_SUCCESS_WITH_INFO) { + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + } else { + EXPECT_EQ(ret, SQL_SUCCESS); + } + + EXPECT_GT(wcslen(info_value), 0); +} + +// Driver Information + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoActiveEnvironments) { + this->connect(); + + validate(this->conn, SQL_ACTIVE_ENVIRONMENTS, static_cast(0)); + + this->disconnect(); +} + +#ifdef SQL_ASYNC_DBC_FUNCTIONS +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAsyncDbcFunctions) { + this->connect(); + + validate(this->conn, SQL_ASYNC_DBC_FUNCTIONS, + static_cast(SQL_ASYNC_DBC_NOT_CAPABLE)); + + this->disconnect(); +} +#endif + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAsyncMode) { + this->connect(); + + validate(this->conn, SQL_ASYNC_MODE, static_cast(SQL_AM_NONE)); + + this->disconnect(); +} + +#ifdef SQL_ASYNC_NOTIFICATION +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAsyncNotification) { + this->connect(); + + validate(this->conn, SQL_ASYNC_NOTIFICATION, + static_cast(SQL_ASYNC_NOTIFICATION_NOT_CAPABLE)); + + this->disconnect(); +} +#endif + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoBatchRowCount) { + this->connect(); + + validate(this->conn, SQL_BATCH_ROW_COUNT, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoBatchSupport) { + this->connect(); + + validate(this->conn, SQL_BATCH_SUPPORT, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDataSourceName) { + this->connect(); + + validate(this->conn, SQL_DATA_SOURCE_NAME, (SQLWCHAR*)L""); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDriverAwarePoolingSupported) { + // A driver does not need to implement SQL_DRIVER_AWARE_POOLING_SUPPORTED and the + // Driver Manager will not honor to the driver's return value. + this->connect(); + + validate(this->conn, SQL_DRIVER_AWARE_POOLING_SUPPORTED, + static_cast(SQL_DRIVER_AWARE_POOLING_NOT_CAPABLE)); + + this->disconnect(); +} + +// These information types are implemented by the Driver Manager alone. +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDriverHdbc) { + this->connect(); + + // Value returned from driver manager is the connection address + validateGreaterThan(this->conn, SQL_DRIVER_HDBC, static_cast(0)); + + this->disconnect(); +} + +// These information types are implemented by the Driver Manager alone. +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDriverHdesc) { + // TODO This is failing due to no descriptor being created + // enable after SQL_HANDLE_DESC is supported + GTEST_SKIP(); + this->connect(); + + validate(this->conn, SQL_DRIVER_HDESC, static_cast(0)); + + this->disconnect(); +} + +// These information types are implemented by the Driver Manager alone. +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDriverHenv) { + this->connect(); + + // Value returned from driver manager is the env address + validateGreaterThan(this->conn, SQL_DRIVER_HENV, static_cast(0)); + + this->disconnect(); +} + +// These information types are implemented by the Driver Manager alone. +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDriverHlib) { + this->connect(); + + validateGreaterThan(this->conn, SQL_DRIVER_HLIB, static_cast(0)); + + this->disconnect(); +} + +// These information types are implemented by the Driver Manager alone. +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDriverHstmt) { + this->connect(); + + // Value returned from driver manager is the stmt address + SQLHSTMT local_stmt = this->stmt; + SQLRETURN ret = SQLGetInfo(this->conn, SQL_DRIVER_HSTMT, &local_stmt, 0, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_GT(local_stmt, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDriverName) { + this->connect(); + + validate(this->conn, SQL_DRIVER_NAME, (SQLWCHAR*)L"Arrow Flight ODBC Driver"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDriverOdbcVer) { + this->connect(); + + validate(this->conn, SQL_DRIVER_ODBC_VER, (SQLWCHAR*)L"03.80"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDriverVer) { + this->connect(); + + validate(this->conn, SQL_DRIVER_VER, (SQLWCHAR*)L"00.09.0000.0"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDynamicCursorAttributes1) { + this->connect(); + + validate(this->conn, SQL_DYNAMIC_CURSOR_ATTRIBUTES1, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDynamicCursorAttributes2) { + this->connect(); + + validate(this->conn, SQL_DYNAMIC_CURSOR_ATTRIBUTES2, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoForwardOnlyCursorAttributes1) { + this->connect(); + + validate(this->conn, SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1, + static_cast(SQL_CA1_NEXT)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoForwardOnlyCursorAttributes2) { + this->connect(); + + validate(this->conn, SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2, + static_cast(SQL_CA2_READ_ONLY_CONCURRENCY)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoFileUsage) { + this->connect(); + + validate(this->conn, SQL_FILE_USAGE, static_cast(SQL_FILE_NOT_SUPPORTED)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoGetDataExtensions) { + this->connect(); + + validate(this->conn, SQL_GETDATA_EXTENSIONS, + static_cast(SQL_GD_ANY_COLUMN | SQL_GD_ANY_ORDER)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoSchemaViews) { + this->connect(); + + validate(this->conn, SQL_INFO_SCHEMA_VIEWS, + static_cast(SQL_ISV_TABLES | SQL_ISV_COLUMNS | SQL_ISV_VIEWS)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoKeysetCursorAttributes1) { + this->connect(); + + validate(this->conn, SQL_KEYSET_CURSOR_ATTRIBUTES1, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoKeysetCursorAttributes2) { + this->connect(); + + validate(this->conn, SQL_KEYSET_CURSOR_ATTRIBUTES2, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxAsyncConcurrentStatements) { + this->connect(); + + validate(this->conn, SQL_MAX_ASYNC_CONCURRENT_STATEMENTS, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxConcurrentActivities) { + this->connect(); + + validate(this->conn, SQL_MAX_CONCURRENT_ACTIVITIES, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxDriverConnections) { + this->connect(); + + validate(this->conn, SQL_MAX_DRIVER_CONNECTIONS, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoOdbcInterfaceConformance) { + this->connect(); + + validate(this->conn, SQL_ODBC_INTERFACE_CONFORMANCE, + static_cast(SQL_OIC_CORE)); + + this->disconnect(); +} + +// case SQL_ODBC_STANDARD_CLI_CONFORMANCE: - mentioned in SQLGetInfo spec with no +// description and there is no constant for this. +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoOdbcStandardCliConformance) { + // Type commented out in odbc_connection.cc + GTEST_SKIP(); + this->connect(); + + // Type does not exist in sql.h + // validate(this->conn, SQL_ODBC_STANDARD_CLI_CONFORMANCE, + // static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoOdbcVer) { + // This is implemented only in the Driver Manager. + this->connect(); + + validate(this->conn, SQL_ODBC_VER, (SQLWCHAR*)L"03.80.0000"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoParamArrayRowCounts) { + this->connect(); + + validate(this->conn, SQL_PARAM_ARRAY_ROW_COUNTS, + static_cast(SQL_PARC_NO_BATCH)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoParamArraySelects) { + this->connect(); + + validate(this->conn, SQL_PARAM_ARRAY_SELECTS, + static_cast(SQL_PAS_NO_SELECT)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoRowUpdates) { + this->connect(); + + validate(this->conn, SQL_ROW_UPDATES, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoSearchPatternEscape) { + this->connect(); + + validate(this->conn, SQL_SEARCH_PATTERN_ESCAPE, (SQLWCHAR*)L"\\"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoServerName) { + this->connect(); + + validateNotEmptySQLWCHAR(this->conn, SQL_SERVER_NAME, false); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoStaticCursorAttributes1) { + this->connect(); + + validate(this->conn, SQL_STATIC_CURSOR_ATTRIBUTES1, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoStaticCursorAttributes2) { + this->connect(); + + validate(this->conn, SQL_STATIC_CURSOR_ATTRIBUTES2, static_cast(0)); + + this->disconnect(); +} + +// DBMS Product Information + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDatabaseName) { + this->connect(); + + validate(this->conn, SQL_DATABASE_NAME, (SQLWCHAR*)L""); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDbmsName) { + this->connect(); + + validateNotEmptySQLWCHAR(this->conn, SQL_DBMS_NAME, false); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDbmsVer) { + this->connect(); + + validateNotEmptySQLWCHAR(this->conn, SQL_DBMS_VER, false); + + this->disconnect(); +} + +// Data Source Information + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAccessibleProcedures) { + this->connect(); + + validate(this->conn, SQL_ACCESSIBLE_PROCEDURES, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAccessibleTables) { + this->connect(); + + validate(this->conn, SQL_ACCESSIBLE_TABLES, (SQLWCHAR*)L"Y"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoBookmarkPersistence) { + this->connect(); + + validate(this->conn, SQL_BOOKMARK_PERSISTENCE, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCatalogTerm) { + this->connect(); + + validate(this->conn, SQL_CATALOG_TERM, (SQLWCHAR*)L""); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCollationSeq) { + this->connect(); + + validate(this->conn, SQL_COLLATION_SEQ, (SQLWCHAR*)L""); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConcatNullBehavior) { + this->connect(); + + validate(this->conn, SQL_CONCAT_NULL_BEHAVIOR, static_cast(SQL_CB_NULL)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCursorCommitBehavior) { + this->connect(); + + validate(this->conn, SQL_CURSOR_COMMIT_BEHAVIOR, + static_cast(SQL_CB_CLOSE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCursorRollbackBehavior) { + this->connect(); + + validate(this->conn, SQL_CURSOR_ROLLBACK_BEHAVIOR, + static_cast(SQL_CB_CLOSE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCursorSensitivity) { + this->connect(); + + validate(this->conn, SQL_CURSOR_SENSITIVITY, static_cast(SQL_UNSPECIFIED)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDataSourceReadOnly) { + this->connect(); + + validate(this->conn, SQL_DATA_SOURCE_READ_ONLY, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDefaultTxnIsolation) { + this->connect(); + + validate(this->conn, SQL_DEFAULT_TXN_ISOLATION, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDescribeParameter) { + this->connect(); + + validate(this->conn, SQL_DESCRIBE_PARAMETER, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMultResultSets) { + this->connect(); + + validate(this->conn, SQL_MULT_RESULT_SETS, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMultipleActiveTxn) { + this->connect(); + + validate(this->conn, SQL_MULTIPLE_ACTIVE_TXN, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoNeedLongDataLen) { + this->connect(); + + validate(this->conn, SQL_NEED_LONG_DATA_LEN, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoNullCollation) { + this->connect(); + + validate(this->conn, SQL_NULL_COLLATION, static_cast(SQL_NC_START)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoProcedureTerm) { + this->connect(); + + validate(this->conn, SQL_PROCEDURE_TERM, (SQLWCHAR*)L""); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoSchemaTerm) { + this->connect(); + + validate(this->conn, SQL_SCHEMA_TERM, (SQLWCHAR*)L"schema"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoScrollOptions) { + this->connect(); + + validate(this->conn, SQL_SCROLL_OPTIONS, static_cast(SQL_SO_FORWARD_ONLY)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoTableTerm) { + this->connect(); + + validate(this->conn, SQL_TABLE_TERM, (SQLWCHAR*)L"table"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoTxnCapable) { + this->connect(); + + validate(this->conn, SQL_TXN_CAPABLE, static_cast(SQL_TC_NONE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoTxnIsolationOption) { + this->connect(); + + validate(this->conn, SQL_TXN_ISOLATION_OPTION, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoUserName) { + this->connect(); + + validate(this->conn, SQL_USER_NAME, (SQLWCHAR*)L""); + + this->disconnect(); +} + +// Supported SQL + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAggregateFunctions) { + this->connect(); + + validate( + this->conn, SQL_AGGREGATE_FUNCTIONS, + static_cast(SQL_AF_ALL | SQL_AF_AVG | SQL_AF_COUNT | SQL_AF_DISTINCT | + SQL_AF_MAX | SQL_AF_MIN | SQL_AF_SUM)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAlterDomain) { + this->connect(); + + validate(this->conn, SQL_ALTER_DOMAIN, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAlterSchema) { + // Type commented out in odbc_connection.cc + GTEST_SKIP(); + this->connect(); + + // Type does not exist in sql.h + // validate(this->conn, SQL_ALTER_SCHEMA, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAlterTable) { + this->connect(); + + validate(this->conn, SQL_ALTER_TABLE, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoAnsiSqlDatetimeLiterals) { + // Type commented out in odbc_connection.cc + GTEST_SKIP(); + this->connect(); + + // Type does not exist in sql.h + // validate(this->conn, SQL_ANSI_SQL_DATETIME_LITERALS, (SQLWCHAR*)L""); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCatalogLocation) { + this->connect(); + + validate(this->conn, SQL_CATALOG_LOCATION, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCatalogName) { + this->connect(); + + validate(this->conn, SQL_CATALOG_NAME, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCatalogNameSeparator) { + this->connect(); + + validate(this->conn, SQL_CATALOG_NAME_SEPARATOR, (SQLWCHAR*)L""); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoCatalogUsage) { + this->connect(); + + validate(this->conn, SQL_CATALOG_USAGE, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoColumnAlias) { + this->connect(); + + validate(this->conn, SQL_COLUMN_ALIAS, (SQLWCHAR*)L"Y"); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoCorrelationName) { + this->connect(); + + validate(this->conn, SQL_CORRELATION_NAME, static_cast(SQL_CN_NONE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCreateAssertion) { + this->connect(); + + validate(this->conn, SQL_CREATE_ASSERTION, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCreateCharacterSet) { + this->connect(); + + validate(this->conn, SQL_CREATE_CHARACTER_SET, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCreateCollation) { + this->connect(); + + validate(this->conn, SQL_CREATE_COLLATION, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCreateDomain) { + this->connect(); + + validate(this->conn, SQL_CREATE_DOMAIN, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoCreateSchema) { + this->connect(); + + validate(this->conn, SQL_CREATE_SCHEMA, static_cast(1)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoCreateTable) { + this->connect(); + + validate(this->conn, SQL_CREATE_TABLE, static_cast(1)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoCreateTranslation) { + this->connect(); + + validate(this->conn, SQL_CREATE_TRANSLATION, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDdlIndex) { + this->connect(); + + validate(this->conn, SQL_DDL_INDEX, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDropAssertion) { + this->connect(); + + validate(this->conn, SQL_DROP_ASSERTION, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDropCharacterSet) { + this->connect(); + + validate(this->conn, SQL_DROP_CHARACTER_SET, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDropCollation) { + this->connect(); + + validate(this->conn, SQL_DROP_COLLATION, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDropDomain) { + this->connect(); + + validate(this->conn, SQL_DROP_DOMAIN, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDropSchema) { + this->connect(); + + validate(this->conn, SQL_DROP_SCHEMA, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDropTable) { + this->connect(); + + validate(this->conn, SQL_DROP_TABLE, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDropTranslation) { + this->connect(); + + validate(this->conn, SQL_DROP_TRANSLATION, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoDropView) { + this->connect(); + + validate(this->conn, SQL_DROP_VIEW, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoExpressionsInOrderby) { + this->connect(); + + validate(this->conn, SQL_EXPRESSIONS_IN_ORDERBY, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoGroupBy) { + this->connect(); + + validate(this->conn, SQL_GROUP_BY, + static_cast(SQL_GB_GROUP_BY_CONTAINS_SELECT)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoIdentifierCase) { + this->connect(); + + validate(this->conn, SQL_IDENTIFIER_CASE, static_cast(SQL_IC_MIXED)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoIdentifierQuoteChar) { + this->connect(); + + validate(this->conn, SQL_IDENTIFIER_QUOTE_CHAR, (SQLWCHAR*)L"\""); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoIndexKeywords) { + this->connect(); + + validate(this->conn, SQL_INDEX_KEYWORDS, static_cast(SQL_IK_NONE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoInsertStatement) { + this->connect(); + + validate(this->conn, SQL_INSERT_STATEMENT, + static_cast(SQL_IS_INSERT_LITERALS | SQL_IS_INSERT_SEARCHED | + SQL_IS_SELECT_INTO)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoIntegrity) { + this->connect(); + + validate(this->conn, SQL_INTEGRITY, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoKeywords) { + this->connect(); + + validateNotEmptySQLWCHAR(this->conn, SQL_KEYWORDS, true); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoLikeEscapeClause) { + this->connect(); + + validate(this->conn, SQL_LIKE_ESCAPE_CLAUSE, (SQLWCHAR*)L"Y"); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoNonNullableColumns) { + this->connect(); + + validate(this->conn, SQL_NON_NULLABLE_COLUMNS, static_cast(SQL_NNC_NULL)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoOjCapabilities) { + this->connect(); + + validate(this->conn, SQL_OJ_CAPABILITIES, + static_cast(SQL_OJ_LEFT | SQL_OJ_RIGHT | SQL_OJ_FULL)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoOrderByColumnsInSelect) { + this->connect(); + + validate(this->conn, SQL_ORDER_BY_COLUMNS_IN_SELECT, (SQLWCHAR*)L"Y"); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoOuterJoins) { + this->connect(); + + validate(this->conn, SQL_OUTER_JOINS, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoProcedures) { + this->connect(); + + validate(this->conn, SQL_PROCEDURES, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoQuotedIdentifierCase) { + this->connect(); + + validate(this->conn, SQL_QUOTED_IDENTIFIER_CASE, + static_cast(SQL_IC_MIXED)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoSchemaUsage) { + this->connect(); + + validate(this->conn, SQL_SCHEMA_USAGE, static_cast(SQL_SU_DML_STATEMENTS)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoSpecialCharacters) { + this->connect(); + + validate(this->conn, SQL_SPECIAL_CHARACTERS, (SQLWCHAR*)L""); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoSqlConformance) { + this->connect(); + + validate(this->conn, SQL_SQL_CONFORMANCE, static_cast(SQL_SC_SQL92_ENTRY)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoSubqueries) { + this->connect(); + + validate(this->conn, SQL_SUBQUERIES, + static_cast(SQL_SQ_CORRELATED_SUBQUERIES | SQL_SQ_COMPARISON | + SQL_SQ_EXISTS | SQL_SQ_IN | SQL_SQ_QUANTIFIED)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoUnion) { + this->connect(); + + validate(this->conn, SQL_UNION, + static_cast(SQL_U_UNION | SQL_U_UNION_ALL)); + + this->disconnect(); +} + +// SQL Limits + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxBinaryLiteralLen) { + this->connect(); + + validate(this->conn, SQL_MAX_BINARY_LITERAL_LEN, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoMaxCatalogNameLen) { + this->connect(); + + validate(this->conn, SQL_MAX_CATALOG_NAME_LEN, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxCharLiteralLen) { + this->connect(); + + validate(this->conn, SQL_MAX_CHAR_LITERAL_LEN, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoMaxColumnNameLen) { + this->connect(); + + validate(this->conn, SQL_MAX_COLUMN_NAME_LEN, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxColumnsInGroupBy) { + this->connect(); + + validate(this->conn, SQL_MAX_COLUMNS_IN_GROUP_BY, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxColumnsInIndex) { + this->connect(); + + validate(this->conn, SQL_MAX_COLUMNS_IN_INDEX, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxColumnsInOrderBy) { + this->connect(); + + validate(this->conn, SQL_MAX_COLUMNS_IN_ORDER_BY, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxColumnsInSelect) { + this->connect(); + + validate(this->conn, SQL_MAX_COLUMNS_IN_SELECT, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxColumnsInTable) { + this->connect(); + + validate(this->conn, SQL_MAX_COLUMNS_IN_TABLE, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoMaxCursorNameLen) { + this->connect(); + + validate(this->conn, SQL_MAX_CURSOR_NAME_LEN, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxIdentifierLen) { + this->connect(); + + validate(this->conn, SQL_MAX_IDENTIFIER_LEN, static_cast(65535)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxIndexSize) { + this->connect(); + + validate(this->conn, SQL_MAX_INDEX_SIZE, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxProcedureNameLen) { + this->connect(); + + validate(this->conn, SQL_MAX_PROCEDURE_NAME_LEN, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxRowSize) { + this->connect(); + + validate(this->conn, SQL_MAX_ROW_SIZE, (SQLWCHAR*)L""); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoMaxRowSizeIncludesLong) { + this->connect(); + + validate(this->conn, SQL_MAX_ROW_SIZE_INCLUDES_LONG, (SQLWCHAR*)L"N"); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoMaxSchemaNameLen) { + this->connect(); + + validate(this->conn, SQL_MAX_SCHEMA_NAME_LEN, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxStatementLen) { + this->connect(); + + validate(this->conn, SQL_MAX_STATEMENT_LEN, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoMaxTableNameLen) { + this->connect(); + + validate(this->conn, SQL_MAX_TABLE_NAME_LEN, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoMaxTablesInSelect) { + this->connect(); + + validate(this->conn, SQL_MAX_TABLES_IN_SELECT, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoMaxUserNameLen) { + this->connect(); + + validate(this->conn, SQL_MAX_USER_NAME_LEN, static_cast(0)); + + this->disconnect(); +} + +// Scalar Function Information + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoConvertFunctions) { + this->connect(); + + validate(this->conn, SQL_CONVERT_FUNCTIONS, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoNumericFunctions) { + this->connect(); + + validate(this->conn, SQL_NUMERIC_FUNCTIONS, static_cast(4058942)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoStringFunctions) { + this->connect(); + + validate(this->conn, SQL_STRING_FUNCTIONS, + static_cast(SQL_FN_STR_LTRIM | SQL_FN_STR_LENGTH | + SQL_FN_STR_REPLACE | SQL_FN_STR_RTRIM)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoSystemFunctions) { + this->connect(); + + validate(this->conn, SQL_SYSTEM_FUNCTIONS, + static_cast(SQL_FN_SYS_IFNULL | SQL_FN_SYS_USERNAME)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoTimedateAddIntervals) { + this->connect(); + + validate(this->conn, SQL_TIMEDATE_ADD_INTERVALS, + static_cast(SQL_FN_TSI_FRAC_SECOND | SQL_FN_TSI_SECOND | + SQL_FN_TSI_MINUTE | SQL_FN_TSI_HOUR | SQL_FN_TSI_DAY | + SQL_FN_TSI_WEEK | SQL_FN_TSI_MONTH | + SQL_FN_TSI_QUARTER | SQL_FN_TSI_YEAR)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoTimedateDiffIntervals) { + this->connect(); + + validate(this->conn, SQL_TIMEDATE_DIFF_INTERVALS, + static_cast(SQL_FN_TSI_FRAC_SECOND | SQL_FN_TSI_SECOND | + SQL_FN_TSI_MINUTE | SQL_FN_TSI_HOUR | SQL_FN_TSI_DAY | + SQL_FN_TSI_WEEK | SQL_FN_TSI_MONTH | + SQL_FN_TSI_QUARTER | SQL_FN_TSI_YEAR)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoTimedateFunctions) { + this->connect(); + + validate(this->conn, SQL_TIMEDATE_FUNCTIONS, + static_cast( + SQL_FN_TD_CURRENT_DATE | SQL_FN_TD_CURRENT_TIME | + SQL_FN_TD_CURRENT_TIMESTAMP | SQL_FN_TD_CURDATE | SQL_FN_TD_CURTIME | + SQL_FN_TD_DAYNAME | SQL_FN_TD_DAYOFMONTH | SQL_FN_TD_DAYOFWEEK | + SQL_FN_TD_DAYOFYEAR | SQL_FN_TD_EXTRACT | SQL_FN_TD_HOUR | + SQL_FN_TD_MINUTE | SQL_FN_TD_MONTH | SQL_FN_TD_MONTHNAME | SQL_FN_TD_NOW | + SQL_FN_TD_QUARTER | SQL_FN_TD_SECOND | SQL_FN_TD_TIMESTAMPADD | + SQL_FN_TD_TIMESTAMPDIFF | SQL_FN_TD_WEEK | SQL_FN_TD_YEAR)); + + this->disconnect(); +} + +// Conversion Information + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoConvertBigint) { + this->connect(); + + validate(this->conn, SQL_CONVERT_BIGINT, static_cast(8)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoConvertBinary) { + this->connect(); + + validate(this->conn, SQL_CONVERT_BINARY, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertBit) { + this->connect(); + + validate(this->conn, SQL_CONVERT_BIT, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoConvertChar) { + this->connect(); + + validate(this->conn, SQL_CONVERT_CHAR, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoConvertDate) { + this->connect(); + + validate(this->conn, SQL_CONVERT_DATE, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoConvertDecimal) { + this->connect(); + + validate(this->conn, SQL_CONVERT_DECIMAL, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertDouble) { + this->connect(); + + validate(this->conn, SQL_CONVERT_DOUBLE, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoConvertFloat) { + this->connect(); + + validate(this->conn, SQL_CONVERT_FLOAT, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertInteger) { + this->connect(); + + validate(this->conn, SQL_CONVERT_INTEGER, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoConvertIntervalDayTime) { + this->connect(); + + validate(this->conn, SQL_CONVERT_INTERVAL_DAY_TIME, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertIntervalYearMonth) { + this->connect(); + + validate(this->conn, SQL_CONVERT_INTERVAL_YEAR_MONTH, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertLongvarbinary) { + this->connect(); + + validate(this->conn, SQL_CONVERT_LONGVARBINARY, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertLongvarchar) { + this->connect(); + + validate(this->conn, SQL_CONVERT_LONGVARCHAR, static_cast(0)); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLGetInfoConvertNumeric) { + this->connect(); + + validate(this->conn, SQL_CONVERT_NUMERIC, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertReal) { + this->connect(); + + validate(this->conn, SQL_CONVERT_REAL, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertSmallint) { + this->connect(); + + validate(this->conn, SQL_CONVERT_SMALLINT, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertTime) { + this->connect(); + + validate(this->conn, SQL_CONVERT_TIME, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertTimestamp) { + this->connect(); + + validate(this->conn, SQL_CONVERT_TIMESTAMP, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertTinyint) { + this->connect(); + + validate(this->conn, SQL_CONVERT_TINYINT, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertVarbinary) { + this->connect(); + + validate(this->conn, SQL_CONVERT_VARBINARY, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetInfoConvertVarchar) { + this->connect(); + + validate(this->conn, SQL_CONVERT_VARCHAR, static_cast(0)); + + this->disconnect(); +} + +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc new file mode 100644 index 000000000000..81c4abe70cfe --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc @@ -0,0 +1,1153 @@ +// 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 "google/protobuf/message_lite.h" +#include "gtest/gtest.h" + +namespace arrow::flight::sql::odbc { + +TEST(SQLAllocHandle, TestSQLAllocHandleEnv) { + // ODBC Environment + SQLHENV env; + + // Allocate an environment handle + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); + + EXPECT_TRUE(env != NULL); +} + +TEST(SQLAllocEnv, TestSQLAllocEnv) { + // ODBC Environment + SQLHENV env; + + // Allocate an environment handle + SQLRETURN return_value = SQLAllocEnv(&env); + + EXPECT_TRUE(return_value == SQL_SUCCESS); +} + +TEST(SQLAllocHandle, TestSQLAllocHandleConnect) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // Allocate an environment handle + SQLRETURN return_value = SQLAllocEnv(&env); + + EXPECT_TRUE(return_value == SQL_SUCCESS); + + // Allocate a connection using alloc handle + SQLRETURN return_alloc_handle = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_TRUE(return_alloc_handle == SQL_SUCCESS); +} + +TEST(SQLAllocConnect, TestSQLAllocHandleConnect) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // Allocate an environment handle + SQLRETURN return_value = SQLAllocEnv(&env); + + EXPECT_TRUE(return_value == SQL_SUCCESS); + + // Allocate a connection using alloc handle + SQLRETURN return_alloc_connect = SQLAllocConnect(env, &conn); + + EXPECT_TRUE(return_alloc_connect == SQL_SUCCESS); +} + +TEST(SQLFreeHandle, TestSQLFreeHandleEnv) { + // ODBC Environment + SQLHENV env; + + // Allocate an environment handle + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); + + // Free an environment handle + SQLRETURN return_value = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_TRUE(return_value == SQL_SUCCESS); +} + +TEST(SQLFreeEnv, TestSQLFreeEnv) { + // ODBC Environment + SQLHENV env; + + // Allocate an environment handle + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env); + + // Free an environment handle + SQLRETURN return_value = SQLFreeEnv(env); + + EXPECT_TRUE(return_value == SQL_SUCCESS); +} + +TEST(SQLFreeHandle, TestSQLFreeHandleConnect) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // Allocate an environment handle + SQLRETURN return_value = SQLAllocEnv(&env); + + EXPECT_TRUE(return_value == SQL_SUCCESS); + + // Allocate a connection using alloc handle + SQLRETURN return_alloc_handle = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_TRUE(return_alloc_handle == SQL_SUCCESS); + + // Free the created connection using free handle + SQLRETURN return_free_handle = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_TRUE(return_free_handle == SQL_SUCCESS); +} + +TEST(SQLFreeConnect, TestSQLFreeConnect) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // Allocate an environment handle + SQLRETURN return_env = SQLAllocEnv(&env); + + EXPECT_TRUE(return_env == SQL_SUCCESS); + + // Allocate a connection using alloc handle + SQLRETURN return_alloc_handle = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_TRUE(return_alloc_handle == SQL_SUCCESS); + + // Free the created connection using free connect + SQLRETURN return_free_connect = SQLFreeConnect(conn); + + EXPECT_TRUE(return_free_connect == SQL_SUCCESS); +} + +TEST(SQLGetEnvAttr, TestSQLGetEnvAttrODBCVersion) { + // ODBC Environment + SQLHENV env; + + SQLINTEGER version; + + // Allocate an environment handle + SQLRETURN return_env = SQLAllocEnv(&env); + + EXPECT_TRUE(return_env == SQL_SUCCESS); + + SQLRETURN return_get = SQLGetEnvAttr(env, SQL_ATTR_ODBC_VERSION, &version, 0, 0); + + EXPECT_TRUE(return_get == SQL_SUCCESS); + + EXPECT_EQ(version, SQL_OV_ODBC2); +} + +TEST(SQLSetEnvAttr, TestSQLSetEnvAttrODBCVersionValid) { + // ODBC Environment + SQLHENV env; + + // Allocate an environment handle + SQLRETURN return_env = SQLAllocEnv(&env); + + EXPECT_TRUE(return_env == SQL_SUCCESS); + + // Attempt to set to unsupported version + SQLRETURN return_set = + SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, reinterpret_cast(SQL_OV_ODBC2), 0); + + EXPECT_TRUE(return_set == SQL_SUCCESS); +} + +TEST(SQLSetEnvAttr, TestSQLSetEnvAttrODBCVersionInvalid) { + // ODBC Environment + SQLHENV env; + + // Allocate an environment handle + SQLRETURN return_env = SQLAllocEnv(&env); + + EXPECT_TRUE(return_env == SQL_SUCCESS); + + // Attempt to set to unsupported version + SQLRETURN return_set = + SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, reinterpret_cast(1), 0); + + EXPECT_TRUE(return_set == SQL_ERROR); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetEnvAttrOutputNTS) { + this->connect(); + + SQLINTEGER output_nts; + + SQLRETURN return_get = SQLGetEnvAttr(this->env, SQL_ATTR_OUTPUT_NTS, &output_nts, 0, 0); + + EXPECT_TRUE(return_get == SQL_SUCCESS); + + EXPECT_EQ(output_nts, SQL_TRUE); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetEnvAttrGetLength) { + // Test is disabled because call to SQLGetEnvAttr is handled by the driver manager on + // Windows. This test case can be potentially used on macOS/Linux + GTEST_SKIP(); + + this->connect(); + + SQLINTEGER length; + + SQLRETURN return_get = + SQLGetEnvAttr(this->env, SQL_ATTR_ODBC_VERSION, nullptr, 0, &length); + + EXPECT_TRUE(return_get == SQL_SUCCESS); + + EXPECT_EQ(length, sizeof(SQLINTEGER)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetEnvAttrNullValuePointer) { + // Test is disabled because call to SQLGetEnvAttr is handled by the driver manager on + // Windows. This test case can be potentially used on macOS/Linux + GTEST_SKIP(); + this->connect(); + + SQLRETURN return_get = + SQLGetEnvAttr(this->env, SQL_ATTR_ODBC_VERSION, nullptr, 0, nullptr); + + EXPECT_TRUE(return_get == SQL_ERROR); + + this->disconnect(); +} + +TEST(SQLSetEnvAttr, TestSQLSetEnvAttrOutputNTSValid) { + // ODBC Environment + SQLHENV env; + + // Allocate an environment handle + SQLRETURN return_env = SQLAllocEnv(&env); + + EXPECT_TRUE(return_env == SQL_SUCCESS); + + // Attempt to set to output nts to supported version + SQLRETURN return_set = + SQLSetEnvAttr(env, SQL_ATTR_OUTPUT_NTS, reinterpret_cast(SQL_TRUE), 0); + + EXPECT_TRUE(return_set == SQL_SUCCESS); +} + +TEST(SQLSetEnvAttr, TestSQLSetEnvAttrOutputNTSInvalid) { + // ODBC Environment + SQLHENV env; + + // Allocate an environment handle + SQLRETURN return_env = SQLAllocEnv(&env); + + EXPECT_TRUE(return_env == SQL_SUCCESS); + + // Attempt to set to output nts to unsupported false + SQLRETURN return_set = + SQLSetEnvAttr(env, SQL_ATTR_OUTPUT_NTS, reinterpret_cast(SQL_FALSE), 0); + + EXPECT_TRUE(return_set == SQL_ERROR); +} + +TEST(SQLSetEnvAttr, TestSQLSetEnvAttrNullValuePointer) { + // ODBC Environment + SQLHENV env; + + // Allocate an environment handle + SQLRETURN return_env = SQLAllocEnv(&env); + + EXPECT_TRUE(return_env == SQL_SUCCESS); + + // Attempt to set using bad data pointer + SQLRETURN return_set = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, nullptr, 0); + + EXPECT_TRUE(return_set == SQL_ERROR); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLDriverConnect) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Connect string + std::string connect_str = this->getConnectionString(); + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE] = L""; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check that outstr has same content as connect_str + std::string out_connection_string = ODBC::SqlWcharToString(outstr, outstrlen); + Connection::ConnPropertyMap out_properties; + Connection::ConnPropertyMap in_properties; + ODBC::ODBCConnection::getPropertiesFromConnString(out_connection_string, + out_properties); + ODBC::ODBCConnection::getPropertiesFromConnString(connect_str, in_properties); + EXPECT_TRUE(compareConnPropertyMap(out_properties, in_properties)); + + // Disconnect from ODBC + ret = SQLDisconnect(conn); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLDriverConnectDsn) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Connect string + std::string connect_str = this->getConnectionString(); + + // Write connection string content into a DSN, + // must succeed before continuing + ASSERT_TRUE(writeDSN(connect_str)); + + std::string dsn(TEST_DSN); + ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn)); + + // Update connection string to use DSN to connect + connect_str = std::string("DSN=") + std::string(TEST_DSN) + + std::string(";driver={Apache Arrow Flight SQL ODBC Driver};"); + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE] = L""; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Remove DSN + EXPECT_TRUE(UnregisterDsn(wdsn)); + + // Disconnect from ODBC + ret = SQLDisconnect(conn); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLDriverConnectInvalidUid) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Invalid connect string + std::string connect_str = getInvalidConnectionString(); + + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + EXPECT_TRUE(ret == SQL_ERROR); + + VerifyOdbcErrorState(SQL_HANDLE_DBC, conn, error_state_28000); + + std::string out_connection_string = ODBC::SqlWcharToString(outstr, outstrlen); + EXPECT_TRUE(out_connection_string.empty()); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLConnect) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Connect string + std::string connect_str = this->getConnectionString(); + + // Write connection string content into a DSN, + // must succeed before continuing + std::string uid(""), pwd(""); + ASSERT_TRUE(writeDSN(connect_str)); + + std::string dsn(TEST_DSN); + ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn)); + ASSERT_OK_AND_ASSIGN(std::wstring wuid, arrow::util::UTF8ToWideString(uid)); + ASSERT_OK_AND_ASSIGN(std::wstring wpwd, arrow::util::UTF8ToWideString(pwd)); + std::vector dsn0(wdsn.begin(), wdsn.end()); + std::vector uid0(wuid.begin(), wuid.end()); + std::vector pwd0(wpwd.begin(), wpwd.end()); + + // Connecting to ODBC server. Empty uid and pwd should be ignored. + ret = SQLConnect(conn, dsn0.data(), static_cast(dsn0.size()), uid0.data(), + static_cast(uid0.size()), pwd0.data(), + static_cast(pwd0.size())); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Remove DSN + EXPECT_TRUE(UnregisterDsn(wdsn)); + + // Disconnect from ODBC + ret = SQLDisconnect(conn); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLConnectInputUidPwd) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Connect string + std::string connect_str = getConnectionString(); + + // Retrieve valid uid and pwd, assumes TEST_CONNECT_STR contains uid and pwd + Connection::ConnPropertyMap properties; + ODBC::ODBCConnection::getPropertiesFromConnString(connect_str, properties); + std::string uid_key("uid"); + std::string pwd_key("pwd"); + std::string uid = properties[uid_key]; + std::string pwd = properties[pwd_key]; + + // Write connection string content without uid and pwd into a DSN, + // must succeed before continuing + properties.erase(uid_key); + properties.erase(pwd_key); + ASSERT_TRUE(writeDSN(properties)); + + std::string dsn(TEST_DSN); + ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn)); + ASSERT_OK_AND_ASSIGN(std::wstring wuid, arrow::util::UTF8ToWideString(uid)); + ASSERT_OK_AND_ASSIGN(std::wstring wpwd, arrow::util::UTF8ToWideString(pwd)); + std::vector dsn0(wdsn.begin(), wdsn.end()); + std::vector uid0(wuid.begin(), wuid.end()); + std::vector pwd0(wpwd.begin(), wpwd.end()); + + // Connecting to ODBC server. + ret = SQLConnect(conn, dsn0.data(), static_cast(dsn0.size()), uid0.data(), + static_cast(uid0.size()), pwd0.data(), + static_cast(pwd0.size())); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Remove DSN + EXPECT_TRUE(UnregisterDsn(wdsn)); + + // Disconnect from ODBC + ret = SQLDisconnect(conn); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLConnectInvalidUid) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Connect string + std::string connect_str = getConnectionString(); + + // Retrieve valid uid and pwd, assumes TEST_CONNECT_STR contains uid and pwd + Connection::ConnPropertyMap properties; + ODBC::ODBCConnection::getPropertiesFromConnString(connect_str, properties); + std::string uid = properties[std::string("uid")]; + std::string pwd = properties[std::string("pwd")]; + + // Append invalid uid to connection string + connect_str += std::string("uid=non_existent_id;"); + + // Write connection string content into a DSN, + // must succeed before continuing + ASSERT_TRUE(writeDSN(connect_str)); + + std::string dsn(TEST_DSN); + ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn)); + ASSERT_OK_AND_ASSIGN(std::wstring wuid, arrow::util::UTF8ToWideString(uid)); + ASSERT_OK_AND_ASSIGN(std::wstring wpwd, arrow::util::UTF8ToWideString(pwd)); + std::vector dsn0(wdsn.begin(), wdsn.end()); + std::vector uid0(wuid.begin(), wuid.end()); + std::vector pwd0(wpwd.begin(), wpwd.end()); + + // Connecting to ODBC server. + ret = SQLConnect(conn, dsn0.data(), static_cast(dsn0.size()), uid0.data(), + static_cast(uid0.size()), pwd0.data(), + static_cast(pwd0.size())); + + // UID specified in DSN will take precedence, + // so connection still fails despite passing valid uid in SQLConnect call + EXPECT_TRUE(ret == SQL_ERROR); + + VerifyOdbcErrorState(SQL_HANDLE_DBC, conn, error_state_28000); + + // Remove DSN + EXPECT_TRUE(UnregisterDsn(wdsn)); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLConnectDSNPrecedence) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Connect string + std::string connect_str = getConnectionString(); + + // Write connection string content into a DSN, + // must succeed before continuing + + // Pass incorrect uid and password to SQLConnect, they will be ignored. + // Assumes TEST_CONNECT_STR contains uid and pwd + std::string uid("non_existent_id"), pwd("non_existent_password"); + ASSERT_TRUE(writeDSN(connect_str)); + + std::string dsn(TEST_DSN); + ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn)); + ASSERT_OK_AND_ASSIGN(std::wstring wuid, arrow::util::UTF8ToWideString(uid)); + ASSERT_OK_AND_ASSIGN(std::wstring wpwd, arrow::util::UTF8ToWideString(pwd)); + std::vector dsn0(wdsn.begin(), wdsn.end()); + std::vector uid0(wuid.begin(), wuid.end()); + std::vector pwd0(wpwd.begin(), wpwd.end()); + + // Connecting to ODBC server. + ret = SQLConnect(conn, dsn0.data(), static_cast(dsn0.size()), uid0.data(), + static_cast(uid0.size()), pwd0.data(), + static_cast(pwd0.size())); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Remove DSN + EXPECT_TRUE(UnregisterDsn(wdsn)); + + // Disconnect from ODBC + ret = SQLDisconnect(conn); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TEST(SQLDisconnect, TestSQLDisconnectWithoutConnection) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Attempt to disconnect without a connection, expect to fail + ret = SQLDisconnect(conn); + + EXPECT_TRUE(ret == SQL_ERROR); + + // Expect ODBC driver manager to return error state + VerifyOdbcErrorState(SQL_HANDLE_DBC, conn, error_state_08003); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagFieldWForConnectFailure) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Invalid connect string + std::string connect_str = this->getInvalidConnectionString(); + + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + EXPECT_TRUE(ret == SQL_ERROR); + + // Retrieve all supported header level and record level data + SQLSMALLINT HEADER_LEVEL = 0; + SQLSMALLINT RECORD_1 = 1; + + // SQL_DIAG_NUMBER + SQLINTEGER diag_number; + SQLSMALLINT diag_number_length; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, HEADER_LEVEL, SQL_DIAG_NUMBER, &diag_number, + sizeof(SQLINTEGER), &diag_number_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(diag_number, 1); + + // SQL_DIAG_SERVER_NAME + SQLWCHAR server_name[ODBC_BUFFER_SIZE]; + SQLSMALLINT server_name_length; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_SERVER_NAME, server_name, + ODBC_BUFFER_SIZE, &server_name_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // SQL_DIAG_MESSAGE_TEXT + SQLWCHAR message_text[ODBC_BUFFER_SIZE]; + SQLSMALLINT message_text_length; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_MESSAGE_TEXT, + message_text, ODBC_BUFFER_SIZE, &message_text_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_text_length, 100); + + // SQL_DIAG_NATIVE + SQLINTEGER diag_native; + SQLSMALLINT diag_native_length; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_NATIVE, &diag_native, + sizeof(diag_native), &diag_native_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(diag_native, 200); + + // SQL_DIAG_SQLSTATE + const SQLSMALLINT sql_state_size = 6; + SQLWCHAR sql_state[sql_state_size]; + SQLSMALLINT sql_state_length; + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_SQLSTATE, sql_state, + sql_state_size * driver::odbcabstraction::GetSqlWCharSize(), + &sql_state_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // 28000 + EXPECT_EQ(sql_state[0], '2'); + EXPECT_EQ(sql_state[1], '8'); + EXPECT_EQ(sql_state[2], '0'); + EXPECT_EQ(sql_state[3], '0'); + EXPECT_EQ(sql_state[4], '0'); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagFieldWForConnectFailureNTS) { + // Test is disabled because driver manager on Windows does not pass through SQL_NTS + // This test case can be potentially used on macOS/Linux + GTEST_SKIP(); + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Invalid connect string + std::string connect_str = this->getInvalidConnectionString(); + + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + EXPECT_TRUE(ret == SQL_ERROR); + + // Retrieve all supported header level and record level data + SQLSMALLINT RECORD_1 = 1; + + // SQL_DIAG_MESSAGE_TEXT SQL_NTS + SQLWCHAR message_text[ODBC_BUFFER_SIZE]; + SQLSMALLINT message_text_length; + + message_text[ODBC_BUFFER_SIZE - 1] = '\0'; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_MESSAGE_TEXT, + message_text, SQL_NTS, &message_text_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_text_length, 100); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagRecForConnectFailure) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Invalid connect string + std::string connect_str = this->getInvalidConnectionString(); + + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + EXPECT_TRUE(ret == SQL_ERROR); + + SQLWCHAR sql_state[6]; + SQLINTEGER native_error; + SQLWCHAR message[ODBC_BUFFER_SIZE]; + SQLSMALLINT message_length; + + ret = SQLGetDiagRec(SQL_HANDLE_DBC, conn, 1, sql_state, &native_error, message, + ODBC_BUFFER_SIZE, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 120); + + EXPECT_EQ(native_error, 200); + + // 28000 + EXPECT_EQ(sql_state[0], '2'); + EXPECT_EQ(sql_state[1], '8'); + EXPECT_EQ(sql_state[2], '0'); + EXPECT_EQ(sql_state[3], '0'); + EXPECT_EQ(sql_state[4], '0'); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestConnect) { + // Verifies connect and disconnect works on its own + this->connect(); + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLAllocFreeStmt) { + this->connect(); + SQLHSTMT statement; + + // Allocate a statement using alloc statement + SQLRETURN ret = SQLAllocStmt(this->conn, &statement); + + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLWCHAR sql_buffer[ODBC_BUFFER_SIZE] = L"SELECT 1"; + ret = SQLExecDirect(statement, sql_buffer, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Close statement handle + ret = SQLFreeStmt(statement, SQL_CLOSE); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free statement handle + ret = SQLFreeStmt(statement, SQL_DROP); + + EXPECT_EQ(ret, SQL_SUCCESS); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestCloseConnectionWithOpenStatement) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + SQLHSTMT statement; + + // 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); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Connect string + std::string connect_str = this->getConnectionString(); + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE] = L""; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a statement using alloc statement + ret = SQLAllocStmt(conn, &statement); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Disconnect from ODBC without closing the statement first + ret = SQLDisconnect(conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +} // namespace arrow::flight::sql::odbc + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} 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 new file mode 100644 index 000000000000..bb92bee0713d --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc @@ -0,0 +1,489 @@ +// 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. + +// For DSN registration. flight_sql_connection.h needs to included first due to conflicts +// with windows.h +#include "arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h" + +#include "arrow/flight/sql/odbc/tests/odbc_test_suite.h" + +// For DSN registration +#include "arrow/flight/sql/odbc/flight_sql/include/flight_sql/config/configuration.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h" + +namespace arrow::flight::sql::odbc { + +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, + reinterpret_cast(static_cast(odbc_ver)), 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +void FlightSQLODBCRemoteTestBase::connect(SQLINTEGER odbc_ver) { + allocEnvConnHandles(odbc_ver); + std::string connect_str = getConnectionString(); + connectWithString(connect_str); +} + +void FlightSQLODBCRemoteTestBase::connectWithString(std::string connect_str) { + // Connect string + std::vector connect_str0(connect_str.begin(), connect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + SQLRETURN ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + // Assert connection is successful before we continue + ASSERT_TRUE(ret == SQL_SUCCESS); + + // Allocate a statement using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt); + + ASSERT_TRUE(ret == SQL_SUCCESS); +} + +void FlightSQLODBCRemoteTestBase::disconnect() { + // Close statement + SQLRETURN ret = SQLFreeHandle(SQL_HANDLE_STMT, stmt); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Disconnect from ODBC + ret = SQLDisconnect(conn); + + if (ret != SQL_SUCCESS) { + std::cerr << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn) << std::endl; + } + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +std::string FlightSQLODBCRemoteTestBase::getConnectionString() { + std::string connect_str = arrow::internal::GetEnvVar(TEST_CONNECT_STR).ValueOrDie(); + return connect_str; +} + +std::string FlightSQLODBCRemoteTestBase::getInvalidConnectionString() { + std::string connect_str = getConnectionString(); + // Append invalid uid to connection string + connect_str += std::string("uid=non_existent_id;"); + return connect_str; +} + +std::wstring FlightSQLODBCRemoteTestBase::getQueryAllDataTypes() { + std::wstring wsql = + LR"( SELECT + -- Numeric types + -128 as stiny_int_min, 127 as stiny_int_max, + 0 as utiny_int_min, 255 as utiny_int_max, + + -32768 as ssmall_int_min, 32767 as ssmall_int_max, + 0 as usmall_int_min, 65535 as usmall_int_max, + + CAST(-2147483648 AS INTEGER) AS sinteger_min, + CAST(2147483647 AS INTEGER) AS sinteger_max, + CAST(0 AS BIGINT) AS uinteger_min, + CAST(4294967295 AS BIGINT) AS uinteger_max, + + CAST(-9223372036854775808 AS BIGINT) AS sbigint_min, + CAST(9223372036854775807 AS BIGINT) AS sbigint_max, + CAST(0 AS BIGINT) AS ubigint_min, + --Use string to represent unsigned big int due to lack of support from + --remote test server + '18446744073709551615' AS ubigint_max, + + CAST(-999999999 AS DECIMAL(38, 0)) AS decimal_negative, + CAST(999999999 AS DECIMAL(38, 0)) AS decimal_positive, + + CAST(-3.40282347E38 AS FLOAT) AS float_min, CAST(3.40282347E38 AS FLOAT) AS float_max, + + CAST(-1.7976931348623157E308 AS DOUBLE) AS double_min, + CAST(1.7976931348623157E308 AS DOUBLE) AS double_max, + + --Boolean + CAST(false AS BOOLEAN) AS bit_false, + CAST(true AS BOOLEAN) AS bit_true, + + --Character types + 'Z' AS c_char, '你' AS c_wchar, + + '你好' AS c_wvarchar, + + 'XYZ' AS c_varchar, + + --Date / timestamp + CAST(DATE '1400-01-01' AS DATE) AS date_min, + CAST(DATE '9999-12-31' AS DATE) AS date_max, + + CAST(TIMESTAMP '1400-01-01 00:00:00' AS TIMESTAMP) AS timestamp_min, + CAST(TIMESTAMP '9999-12-31 23:59:59' AS TIMESTAMP) AS timestamp_max; + )"; + return wsql; +} + +void FlightSQLODBCRemoteTestBase::SetUp() { + if (arrow::internal::GetEnvVar(TEST_CONNECT_STR).ValueOr("").empty()) { + GTEST_SKIP() << "Skipping FlightSQLODBCRemoteTestBase test: TEST_CONNECT_STR not set"; + } +} + +std::string FindTokenInCallHeaders(const CallHeaders& incoming_headers) { + // Lambda function to compare characters without case sensitivity. + auto char_compare = [](const char& char1, const char& char2) { + return (::toupper(char1) == ::toupper(char2)); + }; + + std::string bearer_token(""); + auto authHeader = incoming_headers.find(kAuthHeader); + if (authHeader != incoming_headers.end()) { + const std::string auth_val(authHeader->second); + if (auth_val.size() > kBearerPrefix.length()) { + if (std::equal(auth_val.begin(), auth_val.begin() + kBearerPrefix.length(), + kBearerPrefix.begin(), char_compare)) { + bearer_token = auth_val.substr(kBearerPrefix.length()); + } + } + } + return bearer_token; +} + +void MockServerMiddleware::SendingHeaders(AddCallHeaders* outgoing_headers) { + std::string bearer_token = FindTokenInCallHeaders(incoming_headers_); + *isValid_ = (bearer_token == std::string(test_token)); +} + +Status MockServerMiddlewareFactory::StartCall( + const CallInfo& info, const ServerCallContext& context, + std::shared_ptr* middleware) { + std::string bearer_token = FindTokenInCallHeaders(context.incoming_headers()); + if (bearer_token == std::string(test_token)) { + *middleware = + std::make_shared(context.incoming_headers(), &isValid_); + } else { + return MakeFlightError(FlightStatusCode::Unauthenticated, + "Invalid token for mock server"); + } + + return Status::OK(); +} + +std::string FlightSQLODBCMockTestBase::getConnectionString() { + std::string connect_str( + "driver={Apache Arrow Flight SQL ODBC Driver};HOST=localhost;port=" + + std::to_string(port) + ";token=" + std::string(test_token) + + ";useEncryption=false;"); + return connect_str; +} + +std::string FlightSQLODBCMockTestBase::getInvalidConnectionString() { + std::string connect_str = getConnectionString(); + // Append invalid token to connection string + connect_str += std::string("token=invalid_token;"); + return connect_str; +} + +std::wstring FlightSQLODBCMockTestBase::getQueryAllDataTypes() { + std::wstring wsql = + LR"( SELECT + -- Numeric types + -128 AS stiny_int_min, 127 AS stiny_int_max, + 0 AS utiny_int_min, 255 AS utiny_int_max, + + -32768 AS ssmall_int_min, 32767 AS ssmall_int_max, + 0 AS usmall_int_min, 65535 AS usmall_int_max, + + CAST(-2147483648 AS INTEGER) AS sinteger_min, + CAST(2147483647 AS INTEGER) AS sinteger_max, + CAST(0 AS INTEGER) AS uinteger_min, + CAST(4294967295 AS INTEGER) AS uinteger_max, + + CAST(-9223372036854775808 AS INTEGER) AS sbigint_min, + CAST(9223372036854775807 AS INTEGER) AS sbigint_max, + CAST(0 AS INTEGER) AS ubigint_min, + -- stored as TEXT as SQLite doesn't support unsigned big int + '18446744073709551615' AS ubigint_max, + + CAST('-999999999' AS NUMERIC) AS decimal_negative, + CAST('999999999' AS NUMERIC) AS decimal_positive, + + CAST(-3.40282347E38 AS REAL) AS float_min, + CAST(3.40282347E38 AS REAL) AS float_max, + + CAST(-1.7976931348623157E308 AS REAL) AS double_min, + CAST(1.7976931348623157E308 AS REAL) AS double_max, + + -- Boolean + 0 AS bit_false, + 1 AS bit_true, + + -- Character types + 'Z' AS c_char, + '你' AS c_wchar, + '你好' AS c_wvarchar, + 'XYZ' AS c_varchar, + + DATE('1400-01-01') AS date_min, + DATE('9999-12-31') AS date_max, + + DATETIME('1400-01-01 00:00:00') AS timestamp_min, + DATETIME('9999-12-31 23:59:59') AS timestamp_max; + )"; + 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 + // SQLiteFlightSqlServer::Impl::DoGetTables + ASSERT_OK(server->ExecuteSql(R"( + CREATE TABLE AllTypesTable( + bigint_col INTEGER PRIMARY KEY AUTOINCREMENT, + char_col varchar(100), + varbinary_col BLOB, + double_col REAL); + + INSERT INTO AllTypesTable ( + char_col, + varbinary_col, + double_col) VALUES ( + '1st Row', + X'31737420726F77', + 3.14159 + ); + )")); +} + +void FlightSQLODBCMockTestBase::CreateUnicodeTable() { + std::string unicodeSql = arrow::util::WideStringToUTF8( + LR"( + CREATE TABLE 数据( + 资料 varchar(100)); + + INSERT INTO 数据 (资料) VALUES ('第一行'); + INSERT INTO 数据 (资料) VALUES ('二行'); + INSERT INTO 数据 (资料) VALUES ('3rd Row'); + )") + .ValueOr(""); + ASSERT_OK(server->ExecuteSql(unicodeSql)); +} + +void FlightSQLODBCMockTestBase::SetUp() { + ASSERT_OK_AND_ASSIGN(auto location, Location::ForGrpcTcp("0.0.0.0", 0)); + arrow::flight::FlightServerOptions options(location); + options.auth_handler = std::make_unique(); + options.middleware.push_back( + {"bearer-auth-server", std::make_shared()}); + ASSERT_OK_AND_ASSIGN(server, + arrow::flight::sql::example::SQLiteFlightSqlServer::Create()); + ASSERT_OK(server->Init(options)); + + port = server->port(); + ASSERT_OK_AND_ASSIGN(location, Location::ForGrpcTcp("localhost", port)); + ASSERT_OK_AND_ASSIGN(auto client, arrow::flight::FlightClient::Connect(location)); +} + +void FlightSQLODBCMockTestBase::TearDown() { ASSERT_OK(server->Shutdown()); } + +bool compareConnPropertyMap(Connection::ConnPropertyMap map1, + Connection::ConnPropertyMap map2) { + if (map1.size() != map2.size()) return false; + + for (const auto& [key, value] : map1) { + if (value != map2[key]) return false; + } + + return true; +} + +void VerifyOdbcErrorState(SQLSMALLINT handle_type, SQLHANDLE handle, + std::string_view expected_state) { + using ODBC::SqlWcharToString; + + SQLWCHAR sql_state[7] = {}; + SQLINTEGER native_code; + + SQLWCHAR message[ODBC_BUFFER_SIZE] = {}; + SQLSMALLINT reallen = 0; + + // On Windows, reallen is in bytes. On Linux, reallen is in chars. + // So, not using reallen + SQLGetDiagRec(handle_type, handle, 1, sql_state, &native_code, message, + ODBC_BUFFER_SIZE, &reallen); + + std::string res = SqlWcharToString(sql_state); + + EXPECT_EQ(res, expected_state); +} + +std::string GetOdbcErrorMessage(SQLSMALLINT handle_type, SQLHANDLE handle) { + using ODBC::SqlWcharToString; + + SQLWCHAR sql_state[7] = {}; + SQLINTEGER native_code; + + SQLWCHAR message[ODBC_BUFFER_SIZE] = {}; + SQLSMALLINT reallen = 0; + + // On Windows, reallen is in bytes. On Linux, reallen is in chars. + // So, not using reallen + SQLGetDiagRec(handle_type, handle, 1, sql_state, &native_code, message, + ODBC_BUFFER_SIZE, &reallen); + + std::string res = SqlWcharToString(sql_state); + + if (res.empty() || !message[0]) { + res = "Cannot find ODBC error message"; + } else { + res.append(": ").append(SqlWcharToString(message)); + } + + return res; +} + +bool writeDSN(std::string connection_str) { + Connection::ConnPropertyMap properties; + + ODBC::ODBCConnection::getPropertiesFromConnString(connection_str, properties); + return writeDSN(properties); +} + +bool writeDSN(Connection::ConnPropertyMap properties) { + using driver::flight_sql::FlightSqlConnection; + using driver::flight_sql::config::Configuration; + using driver::odbcabstraction::Connection; + using ODBC::ODBCConnection; + + Configuration config; + config.Set(FlightSqlConnection::DSN, std::string(TEST_DSN)); + + for (const auto& [key, value] : properties) { + config.Set(key, value); + } + + std::string driver = config.Get(FlightSqlConnection::DRIVER); + std::wstring wDriver = arrow::util::UTF8ToWideString(driver).ValueOr(L""); + return RegisterDsn(config, wDriver.c_str()); +} + +std::wstring ConvertToWString(const std::vector& strVal, SQLSMALLINT strLen) { + std::wstring attrStr; + if (strLen == 0) { + attrStr = std::wstring(&strVal[0]); + } else { + EXPECT_GT(strLen, 0); + EXPECT_LE(strLen, static_cast(ODBC_BUFFER_SIZE)); + attrStr = + std::wstring(strVal.begin(), strVal.begin() + strLen / ODBC::GetSqlWCharSize()); + } + return attrStr; +} + +void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected) { + SQLWCHAR buf[1024]; + SQLLEN bufLen = sizeof(buf) * ODBC::GetSqlWCharSize(); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, bufLen, &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(bufLen, 0); + + // returned bufLen is in bytes so convert to length in characters + size_t charCount = static_cast(bufLen) / ODBC::GetSqlWCharSize(); + std::wstring returned(buf, buf + charCount); + + EXPECT_EQ(returned, expected); +} + +void CheckNullColumnW(SQLHSTMT stmt, int colId) { + SQLWCHAR buf[1024]; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, bufLen, &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(bufLen, SQL_NULL_DATA); +} + +void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& expected) { + SQLINTEGER buf; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_LONG, &buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(buf, expected); +} + +void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& expected) { + SQLSMALLINT buf; + SQLLEN bufLen = sizeof(buf); + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_SSHORT, &buf, sizeof(buf), &bufLen); + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(buf, expected); +} + +void ValidateFetch(SQLHSTMT stmt, SQLRETURN expectedReturn) { + SQLRETURN ret = SQLFetch(stmt); + + EXPECT_EQ(ret, expectedReturn); +} + +} // 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 new file mode 100644 index 000000000000..ac8568900d54 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h @@ -0,0 +1,235 @@ +// 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/testing/gtest_util.h" +#include "arrow/util/io_util.h" +#include "arrow/util/utf8.h" + +#include "arrow/flight/server_middleware.h" +#include "arrow/flight/sql/client.h" +#include "arrow/flight/sql/example/sqlite_server.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/encoding_utils.h" + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include + +#include + +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h" + +// For DSN registration +#include "arrow/flight/sql/odbc/flight_sql/system_dsn.h" + +#define TEST_CONNECT_STR "ARROW_FLIGHT_SQL_ODBC_CONN" +#define TEST_DSN "Apache Arrow Flight SQL Test DSN" + +namespace arrow::flight::sql::odbc { +using driver::odbcabstraction::Connection; + +class FlightSQLODBCRemoteTestBase : public ::testing::Test { + public: + /// \brief Allocate environment and connection handles + 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. + /// 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 + void disconnect(); + /// \brief Get connection string from environment variable "ARROW_FLIGHT_SQL_ODBC_CONN" + std::string virtual getConnectionString(); + /// \brief Get invalid connection string based on connection string defined in + /// environment variable "ARROW_FLIGHT_SQL_ODBC_CONN" + std::string virtual getInvalidConnectionString(); + /// \brief Return a SQL query that selects all data types + std::wstring virtual getQueryAllDataTypes(); + + /** ODBC Environment. */ + SQLHENV env; + + /** ODBC Connect. */ + SQLHDBC conn; + + /** ODBC Statement. */ + SQLHSTMT stmt; + + protected: + void SetUp() override; +}; + +static constexpr std::string_view kAuthHeader = "authorization"; +static constexpr std::string_view kBearerPrefix = "Bearer "; +static constexpr std::string_view test_token = "t0k3n"; + +std::string FindTokenInCallHeaders(const CallHeaders& incoming_headers); + +// A server middleware for validating incoming bearer header authentication. +class MockServerMiddleware : public ServerMiddleware { + public: + explicit MockServerMiddleware(const CallHeaders& incoming_headers, bool* isValid) + : isValid_(isValid) { + incoming_headers_ = incoming_headers; + } + + void SendingHeaders(AddCallHeaders* outgoing_headers) override; + + void CallCompleted(const Status& status) override {} + + std::string name() const override { return "MockServerMiddleware"; } + + private: + CallHeaders incoming_headers_; + bool* isValid_; +}; + +// Factory for base64 header authentication testing. +class MockServerMiddlewareFactory : public ServerMiddlewareFactory { + public: + MockServerMiddlewareFactory() : isValid_(false) {} + + Status StartCall(const CallInfo& info, const ServerCallContext& context, + std::shared_ptr* middleware) override; + + private: + bool isValid_; +}; + +class FlightSQLODBCMockTestBase : public FlightSQLODBCRemoteTestBase { + // Sets up a mock server for each test case + public: + /// \brief Get connection string for mock server + std::string getConnectionString() override; + /// \brief Get invalid connection string for mock server + std::string getInvalidConnectionString() override; + /// \brief Return a SQL query that selects all data types + std::wstring getQueryAllDataTypes() override; + + /// \brief Run a SQL query to create default table for table test cases + 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; + + protected: + void SetUp() override; + + void TearDown() override; + + private: + std::shared_ptr server; +}; + +template +class FlightSQLODBCTestBase : public T { + public: + using List = std::list; +}; + +using TestTypes = + ::testing::Types; +TYPED_TEST_SUITE(FlightSQLODBCTestBase, TestTypes); + +/** ODBC read buffer size. */ +enum { ODBC_BUFFER_SIZE = 1024 }; + +/// Compare ConnPropertyMap, key value is case-insensitive +bool compareConnPropertyMap(Connection::ConnPropertyMap map1, + Connection::ConnPropertyMap map2); + +/// Get error message from ODBC driver using SQLGetDiagRec +std::string GetOdbcErrorMessage(SQLSMALLINT handle_type, SQLHANDLE handle); + +static constexpr std::string_view error_state_01004 = "01004"; +static constexpr std::string_view error_state_01S07 = "01S07"; +static constexpr std::string_view error_state_01S02 = "01S02"; +static constexpr std::string_view error_state_07009 = "07009"; +static constexpr std::string_view error_state_08003 = "08003"; +static constexpr std::string_view error_state_22002 = "22002"; +static constexpr std::string_view error_state_24000 = "24000"; +static constexpr std::string_view error_state_28000 = "28000"; +static constexpr std::string_view error_state_HY000 = "HY000"; +static constexpr std::string_view error_state_HY009 = "HY009"; +static constexpr std::string_view error_state_HY010 = "HY010"; +static constexpr std::string_view error_state_HY017 = "HY017"; +static constexpr std::string_view error_state_HY024 = "HY024"; +static constexpr std::string_view error_state_HY090 = "HY090"; +static constexpr std::string_view error_state_HY091 = "HY091"; +static constexpr std::string_view error_state_HY092 = "HY092"; +static constexpr std::string_view error_state_HY106 = "HY106"; +static constexpr std::string_view error_state_HY114 = "HY114"; +static constexpr std::string_view error_state_HY118 = "HY118"; +static constexpr std::string_view error_state_HYC00 = "HYC00"; + +/// Verify ODBC Error State +void VerifyOdbcErrorState(SQLSMALLINT handle_type, SQLHANDLE handle, + std::string_view expected_state); + +/// \brief Write connection string into DSN +/// \param[in] connection_str the connection string. +/// \return true on success +bool writeDSN(std::string connection_str); + +/// \brief Write properties map into DSN +/// \param[in] properties map. +/// \return true on success +bool writeDSN(Connection::ConnPropertyMap properties); + +/// \brief Check wide char vector and convert into wstring +/// \param[in] strVal Vector of SQLWCHAR. +/// \param[in] strLen length of string, in bytes. +/// \return wstring +std::wstring ConvertToWString(const std::vector& strVal, SQLSMALLINT strLen); + +/// \brief Check wide string column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] expected Expected value. +void CheckStringColumnW(SQLHSTMT stmt, int colId, const std::wstring& expected); + +/// \brief Check wide string column value is null. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +void CheckNullColumnW(SQLHSTMT stmt, int colId); + +/// \brief Check int column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] expected Expected value. +void CheckIntColumn(SQLHSTMT stmt, int colId, const SQLINTEGER& expected); + +/// \brief Check smallint column. +/// \param[in] stmt Statement. +/// \param[in] colId Column ID to check. +/// \param[in] expected Expected value. +void CheckSmallIntColumn(SQLHSTMT stmt, int colId, const SQLSMALLINT& expected); + +/// \brief Check sql return against expected. +/// \param[in] stmt Statement. +/// \param[in] expected Expected return. +void ValidateFetch(SQLHSTMT stmt, SQLRETURN expected); +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/statement_attr_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/statement_attr_test.cc new file mode 100644 index 000000000000..aff661dec00b --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_attr_test.cc @@ -0,0 +1,893 @@ +// 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" + +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/statement.h" + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace arrow::flight::sql::odbc { + +// Helper Functions + +// Validate SQLULEN return value +void validateGetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, + SQLULEN expected_value) { + SQLULEN value = 0; + SQLINTEGER stringLength = 0; + + SQLRETURN ret = + SQLGetStmtAttr(statement, attribute, &value, sizeof(value), &stringLength); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(value, expected_value); +} + +// Validate SQLLEN return value +void validateGetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, + SQLLEN expected_value) { + SQLLEN value = 0; + SQLINTEGER stringLength = 0; + + SQLRETURN ret = + SQLGetStmtAttr(statement, attribute, &value, sizeof(value), &stringLength); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(value, expected_value); +} + +// Validate SQLPOINTER return value +void validateGetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, + SQLPOINTER expected_value) { + SQLPOINTER value = nullptr; + SQLINTEGER stringLength = 0; + + SQLRETURN ret = + SQLGetStmtAttr(statement, attribute, &value, sizeof(value), &stringLength); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(value, expected_value); +} + +// Validate unsigned length SQLULEN return value is greater than +void validateGetStmtAttrGreaterThan(SQLHSTMT statement, SQLINTEGER attribute, + SQLULEN compared_value) { + SQLULEN value = 0; + SQLINTEGER stringLengthPtr; + + SQLRETURN ret = SQLGetStmtAttr(statement, attribute, &value, 0, &stringLengthPtr); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(value, compared_value); +} + +// Validate error return value and code +void validateGetStmtAttrErrorCode(SQLHSTMT statement, SQLINTEGER attribute, + std::string_view error_code) { + SQLULEN value = 0; + SQLINTEGER stringLengthPtr; + + SQLRETURN ret = SQLGetStmtAttr(statement, attribute, &value, 0, &stringLengthPtr); + + EXPECT_EQ(ret, SQL_ERROR); + + VerifyOdbcErrorState(SQL_HANDLE_STMT, statement, error_code); +} + +// Validate return value for call to SQLSetStmtAttr with SQLULEN +void validateSetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLULEN new_value) { + SQLINTEGER stringLengthPtr = sizeof(SQLULEN); + + SQLRETURN ret = SQLSetStmtAttr( + statement, attribute, reinterpret_cast(new_value), stringLengthPtr); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +// Validate return value for call to SQLSetStmtAttr with SQLLEN +void validateSetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLLEN new_value) { + SQLINTEGER stringLengthPtr = sizeof(SQLLEN); + + SQLRETURN ret = SQLSetStmtAttr( + statement, attribute, reinterpret_cast(new_value), stringLengthPtr); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +// Validate return value for call to SQLSetStmtAttr with SQLPOINTER +void validateSetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLPOINTER value) { + SQLRETURN ret = SQLSetStmtAttr(statement, attribute, value, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +// Validate error return value and code +void validateSetStmtAttrErrorCode(SQLHSTMT statement, SQLINTEGER attribute, + SQLULEN new_value, std::string_view error_code) { + SQLINTEGER stringLengthPtr = sizeof(SQLULEN); + + SQLRETURN ret = SQLSetStmtAttr( + statement, attribute, reinterpret_cast(new_value), stringLengthPtr); + + EXPECT_EQ(ret, SQL_ERROR); + + VerifyOdbcErrorState(SQL_HANDLE_STMT, statement, error_code); +} + +// Test Cases + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrAppParamDesc) { + this->connect(); + + validateGetStmtAttrGreaterThan(this->stmt, SQL_ATTR_APP_PARAM_DESC, + static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrAppRowDesc) { + this->connect(); + + validateGetStmtAttrGreaterThan(this->stmt, SQL_ATTR_APP_ROW_DESC, + static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrAsyncEnable) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ASYNC_ENABLE, + static_cast(SQL_ASYNC_ENABLE_OFF)); + + this->disconnect(); +} + +#ifdef SQL_ATTR_ASYNC_STMT_EVENT +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrAsyncStmtEventUnsupported) { + this->connect(); + + // Optional feature not implemented + validateGetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_EVENT, error_state_HYC00); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrAsyncStmtPCCallbackUnsupported) { + this->connect(); + + // Optional feature not implemented + validateGetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_PCALLBACK, + error_state_HYC00); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrAsyncStmtPCContextUnsupported) { + this->connect(); + + // Optional feature not implemented + validateGetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_PCONTEXT, + error_state_HYC00); + + this->disconnect(); +} +#endif + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrConcurrency) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_CONCURRENCY, + static_cast(SQL_CONCUR_READ_ONLY)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrCursorScrollable) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_CURSOR_SCROLLABLE, + static_cast(SQL_NONSCROLLABLE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrCursorSensitivity) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_CURSOR_SENSITIVITY, + static_cast(SQL_UNSPECIFIED)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrCursorType) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_CURSOR_TYPE, + static_cast(SQL_CURSOR_FORWARD_ONLY)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrEnableAutoIPD) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ENABLE_AUTO_IPD, + static_cast(SQL_FALSE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrFetchBookmarkPointer) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_FETCH_BOOKMARK_PTR, static_cast(NULL)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrIMPParamDesc) { + this->connect(); + + validateGetStmtAttrGreaterThan(this->stmt, SQL_ATTR_IMP_PARAM_DESC, + static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrIMPRowDesc) { + this->connect(); + + validateGetStmtAttrGreaterThan(this->stmt, SQL_ATTR_IMP_ROW_DESC, + static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrKeysetSize) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_KEYSET_SIZE, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrMaxLength) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_MAX_LENGTH, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrMaxRows) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_MAX_ROWS, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrMetadataID) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_METADATA_ID, static_cast(SQL_FALSE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrNoscan) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_NOSCAN, static_cast(SQL_NOSCAN_OFF)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrParamBindOffsetPtr) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, + static_cast(nullptr)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrParamBindType) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_TYPE, + static_cast(SQL_PARAM_BIND_BY_COLUMN)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrParamOperationPtr) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAM_OPERATION_PTR, + static_cast(nullptr)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrParamStatusPtr) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAM_STATUS_PTR, + static_cast(nullptr)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrParamsProcessedPtr) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAMS_PROCESSED_PTR, + static_cast(nullptr)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrParamsetSize) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAMSET_SIZE, static_cast(1)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrQueryTimeout) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_QUERY_TIMEOUT, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrRetrieveData) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_RETRIEVE_DATA, + static_cast(SQL_RD_ON)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrRowArraySize) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROW_ARRAY_SIZE, static_cast(1)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrRowBindOffsetPtr) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_OFFSET_PTR, + static_cast(nullptr)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrRowBindType) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_TYPE, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrRowNumber) { + this->connect(); + + std::wstring wsql = L"SELECT 1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + + EXPECT_EQ(ret, SQL_SUCCESS); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROW_NUMBER, static_cast(1)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrRowOperationPtr) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROW_OPERATION_PTR, + static_cast(nullptr)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrRowStatusPtr) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROW_STATUS_PTR, + static_cast(nullptr)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrRowsFetchedPtr) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROWS_FETCHED_PTR, + static_cast(nullptr)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrSimulateCursor) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_SIMULATE_CURSOR, + static_cast(SQL_SC_UNIQUE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrUseBookmarks) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ATTR_USE_BOOKMARKS, + static_cast(SQL_UB_OFF)); + + this->disconnect(); +} + +// This is a pre ODBC 3 attribute +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetStmtAttrRowsetSize) { + this->connect(); + + validateGetStmtAttr(this->stmt, SQL_ROWSET_SIZE, static_cast(1)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrAppParamDesc) { + SQLULEN app_param_desc = 0; + SQLINTEGER stringLengthPtr; + this->connect(); + + SQLRETURN ret = SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC, &app_param_desc, 0, + &stringLengthPtr); + + EXPECT_EQ(ret, SQL_SUCCESS); + + validateSetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC, static_cast(0)); + + validateSetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC, + static_cast(app_param_desc)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrAppRowDesc) { + SQLULEN app_row_desc = 0; + SQLINTEGER stringLengthPtr; + this->connect(); + + SQLRETURN ret = SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, &app_row_desc, 0, + &stringLengthPtr); + + EXPECT_EQ(ret, SQL_SUCCESS); + + validateSetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, static_cast(0)); + + validateSetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, + static_cast(app_row_desc)); + + this->disconnect(); +} + +#ifdef SQL_ATTR_ASYNC_ENABLE +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrAsyncEnableUnsupported) { + this->connect(); + + // Optional feature not implemented + validateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_ENABLE, SQL_ASYNC_ENABLE_OFF, + error_state_HYC00); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_STMT_EVENT +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrAsyncStmtEventUnsupported) { + this->connect(); + + // Driver does not support asynchronous notification + validateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_EVENT, 0, + error_state_HY118); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrAsyncStmtPCCallbackUnsupported) { + this->connect(); + + validateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_PCALLBACK, 0, + error_state_HYC00); + + this->disconnect(); +} +#endif + +#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrAsyncStmtPCContextUnsupported) { + this->connect(); + + // Optional feature not implemented + validateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_PCONTEXT, 0, + error_state_HYC00); + + this->disconnect(); +} +#endif + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrConcurrency) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_CONCURRENCY, + static_cast(SQL_CONCUR_READ_ONLY)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrCursorScrollable) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_CURSOR_SCROLLABLE, + static_cast(SQL_NONSCROLLABLE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrCursorSensitivity) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_CURSOR_SENSITIVITY, + static_cast(SQL_UNSPECIFIED)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrCursorType) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_CURSOR_TYPE, + static_cast(SQL_CURSOR_FORWARD_ONLY)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrEnableAutoIPD) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_ENABLE_AUTO_IPD, + static_cast(SQL_FALSE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrFetchBookmarkPointer) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_FETCH_BOOKMARK_PTR, static_cast(NULL)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrIMPParamDesc) { + this->connect(); + + // Invalid use of an automatically allocated descriptor handle + validateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_IMP_PARAM_DESC, + static_cast(0), error_state_HY017); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrIMPRowDesc) { + this->connect(); + + // Invalid use of an automatically allocated descriptor handle + validateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_IMP_ROW_DESC, static_cast(0), + error_state_HY017); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrKeysetSizeUnsupported) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_KEYSET_SIZE, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrMaxLength) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_MAX_LENGTH, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrMaxRows) { + this->connect(); + + // Cannot set read-only attribute + validateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_MAX_ROWS, static_cast(0), + error_state_HY092); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrMetadataID) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_METADATA_ID, static_cast(SQL_FALSE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrNoscan) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_NOSCAN, static_cast(SQL_NOSCAN_OFF)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrParamBindOffsetPtr) { + this->connect(); + + SQLULEN offset = 1000; + + validateSetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, + static_cast(&offset)); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, + static_cast(&offset)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrParamBindType) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_TYPE, + static_cast(SQL_PARAM_BIND_BY_COLUMN)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrParamOperationPtr) { + this->connect(); + + constexpr SQLULEN param_set_size = 4; + SQLUSMALLINT param_operations[param_set_size] = {SQL_PARAM_PROCEED, SQL_PARAM_IGNORE, + SQL_PARAM_PROCEED, SQL_PARAM_IGNORE}; + + validateSetStmtAttr(this->stmt, SQL_ATTR_PARAM_OPERATION_PTR, + static_cast(param_operations)); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAM_OPERATION_PTR, + static_cast(param_operations)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrParamStatusPtr) { + this->connect(); + + // Driver does not support parameters, so just check array can be saved/retrieved + constexpr SQLULEN param_status_size = 4; + SQLUSMALLINT param_status[param_status_size] = {SQL_PARAM_PROCEED, SQL_PARAM_IGNORE, + SQL_PARAM_PROCEED, SQL_PARAM_IGNORE}; + + validateSetStmtAttr(this->stmt, SQL_ATTR_PARAM_STATUS_PTR, + static_cast(param_status)); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAM_STATUS_PTR, + static_cast(param_status)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrParamsProcessedPtr) { + this->connect(); + + SQLULEN processed_count = 0; + + validateSetStmtAttr(this->stmt, SQL_ATTR_PARAMS_PROCESSED_PTR, + static_cast(&processed_count)); + + validateGetStmtAttr(this->stmt, SQL_ATTR_PARAMS_PROCESSED_PTR, + static_cast(&processed_count)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrParamsetSize) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_PARAMSET_SIZE, static_cast(1)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrQueryTimeout) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_QUERY_TIMEOUT, static_cast(1)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrRetrieveData) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_RETRIEVE_DATA, + static_cast(SQL_RD_ON)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrRowArraySize) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_ROW_ARRAY_SIZE, static_cast(1)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrRowBindOffsetPtr) { + this->connect(); + + SQLULEN offset = 1000; + + validateSetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_OFFSET_PTR, + static_cast(&offset)); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_OFFSET_PTR, + static_cast(&offset)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrRowBindType) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_TYPE, static_cast(0)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrRowNumber) { + this->connect(); + + // Cannot set read-only attribute + validateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ROW_NUMBER, static_cast(0), + error_state_HY092); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrRowOperationPtr) { + this->connect(); + + constexpr SQLULEN param_set_size = 4; + SQLUSMALLINT row_operations[param_set_size] = {SQL_ROW_PROCEED, SQL_ROW_IGNORE, + SQL_ROW_PROCEED, SQL_ROW_IGNORE}; + + validateSetStmtAttr(this->stmt, SQL_ATTR_ROW_OPERATION_PTR, + static_cast(row_operations)); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROW_OPERATION_PTR, + static_cast(row_operations)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrRowStatusPtr) { + this->connect(); + + constexpr SQLULEN row_status_size = 4; + SQLUSMALLINT values[4] = {0, 0, 0, 0}; + + validateSetStmtAttr(this->stmt, SQL_ATTR_ROW_STATUS_PTR, + static_cast(values)); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROW_STATUS_PTR, + static_cast(values)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrRowsFetchedPtr) { + this->connect(); + + SQLULEN rows_fetched = 1; + + validateSetStmtAttr(this->stmt, SQL_ATTR_ROWS_FETCHED_PTR, + static_cast(&rows_fetched)); + + validateGetStmtAttr(this->stmt, SQL_ATTR_ROWS_FETCHED_PTR, + static_cast(&rows_fetched)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrSimulateCursor) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_SIMULATE_CURSOR, + static_cast(SQL_SC_UNIQUE)); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrUseBookmarks) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ATTR_USE_BOOKMARKS, + static_cast(SQL_UB_OFF)); + + this->disconnect(); +} + +// This is a pre ODBC 3 attribute +TYPED_TEST(FlightSQLODBCTestBase, TestSQLSetStmtAttrRowsetSize) { + this->connect(); + + validateSetStmtAttr(this->stmt, SQL_ROWSET_SIZE, static_cast(1)); + + 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 new file mode 100644 index 000000000000..0d255101db39 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc @@ -0,0 +1,2603 @@ +// 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 + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace arrow::flight::sql::odbc { +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectSimpleQuery) { + this->connect(); + + std::wstring wsql = L"SELECT 1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Verify 1 is returned + EXPECT_EQ(val, 1); + + ret = SQLFetch(this->stmt); + + EXPECT_EQ(ret, SQL_NO_DATA); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + // Invalid cursor state + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_24000); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectInvalidQuery) { + this->connect(); + + std::wstring wsql = L"SELECT;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + + EXPECT_EQ(ret, SQL_ERROR); + // ODBC provides generic error code HY000 to all statement errors + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY000); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecuteSimpleQuery) { + this->connect(); + + std::wstring wsql = L"SELECT 1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = SQLPrepare(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLExecute(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Fetch data + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Verify 1 is returned + EXPECT_EQ(val, 1); + + ret = SQLFetch(this->stmt); + + EXPECT_EQ(ret, SQL_NO_DATA); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + // Invalid cursor state + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_24000); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLPrepareInvalidQuery) { + this->connect(); + + std::wstring wsql = L"SELECT;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = SQLPrepare(this->stmt, &sql0[0], static_cast(sql0.size())); + + EXPECT_EQ(ret, SQL_ERROR); + // ODBC provides generic error code HY000 to all statement errors + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY000); + + ret = SQLExecute(this->stmt); + // Verify function sequence error state is returned + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY010); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectDataQuery) { + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Numeric Types + + // Signed Tiny Int + int8_t stiny_int_val; + SQLLEN buf_len = sizeof(stiny_int_val); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_STINYINT, &stiny_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(stiny_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 2, SQL_C_STINYINT, &stiny_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(stiny_int_val, std::numeric_limits::max()); + + // Unsigned Tiny Int + uint8_t utiny_int_val; + buf_len = sizeof(utiny_int_val); + + ret = SQLGetData(this->stmt, 3, SQL_C_UTINYINT, &utiny_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(utiny_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 4, SQL_C_UTINYINT, &utiny_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(utiny_int_val, std::numeric_limits::max()); + + // Signed Small Int + int16_t ssmall_int_val; + buf_len = sizeof(ssmall_int_val); + + ret = SQLGetData(this->stmt, 5, SQL_C_SSHORT, &ssmall_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ssmall_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 6, SQL_C_SSHORT, &ssmall_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ssmall_int_val, std::numeric_limits::max()); + + // Unsigned Small Int + uint16_t usmall_int_val; + buf_len = sizeof(usmall_int_val); + + ret = SQLGetData(this->stmt, 7, SQL_C_USHORT, &usmall_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(usmall_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 8, SQL_C_USHORT, &usmall_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(usmall_int_val, std::numeric_limits::max()); + + // Signed Integer + SQLINTEGER slong_val; + buf_len = sizeof(slong_val); + + ret = SQLGetData(this->stmt, 9, SQL_C_SLONG, &slong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 10, SQL_C_SLONG, &slong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::max()); + + // Unsigned Integer + SQLUINTEGER ulong_val; + buf_len = sizeof(ulong_val); + + ret = SQLGetData(this->stmt, 11, SQL_C_ULONG, &ulong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ulong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 12, SQL_C_ULONG, &ulong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ulong_val, std::numeric_limits::max()); + + // Signed Big Int + SQLBIGINT sbig_int_val; + buf_len = sizeof(sbig_int_val); + + ret = SQLGetData(this->stmt, 13, SQL_C_SBIGINT, &sbig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 14, SQL_C_SBIGINT, &sbig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::max()); + + // Unsigned Big Int + SQLUBIGINT ubig_int_val; + buf_len = sizeof(ubig_int_val); + + ret = SQLGetData(this->stmt, 15, SQL_C_UBIGINT, &ubig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ubig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 16, SQL_C_UBIGINT, &ubig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ubig_int_val, std::numeric_limits::max()); + + // Decimal + SQL_NUMERIC_STRUCT decimal_val; + memset(&decimal_val, 0, sizeof(decimal_val)); + buf_len = sizeof(SQL_NUMERIC_STRUCT); + + ret = SQLGetData(this->stmt, 17, SQL_C_NUMERIC, &decimal_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for negative decimal_val value + EXPECT_EQ(decimal_val.sign, 0); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + memset(&decimal_val, 0, sizeof(decimal_val)); + ret = SQLGetData(this->stmt, 18, SQL_C_NUMERIC, &decimal_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for positive decimal_val value + EXPECT_EQ(decimal_val.sign, 1); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + // Float + float float_val; + buf_len = sizeof(float_val); + + ret = SQLGetData(this->stmt, 19, SQL_C_FLOAT, &float_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative float value + EXPECT_EQ(float_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 20, SQL_C_FLOAT, &float_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(float_val, std::numeric_limits::max()); + + // Double + SQLDOUBLE double_val; + buf_len = sizeof(double_val); + + ret = SQLGetData(this->stmt, 21, SQL_C_DOUBLE, &double_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative double value + EXPECT_EQ(double_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 22, SQL_C_DOUBLE, &double_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(double_val, std::numeric_limits::max()); + + // Bit + bool bit_val; + buf_len = sizeof(bit_val); + + ret = SQLGetData(this->stmt, 23, SQL_C_BIT, &bit_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, false); + + ret = SQLGetData(this->stmt, 24, SQL_C_BIT, &bit_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, true); + + // Characters + + // Char + SQLCHAR char_val[2]; + buf_len = sizeof(SQLCHAR) * 2; + + ret = SQLGetData(this->stmt, 25, SQL_C_CHAR, &char_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(char_val[0], 'Z'); + + // WChar + SQLWCHAR wchar_val[2]; + constexpr size_t wchar_size = driver::odbcabstraction::GetSqlWCharSize(); + buf_len = wchar_size * 2; + + ret = SQLGetData(this->stmt, 26, SQL_C_WCHAR, &wchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wchar_val[0], L'你'); + + // WVarchar + SQLWCHAR wvarchar_val[3]; + buf_len = wchar_size * 3; + + ret = SQLGetData(this->stmt, 27, SQL_C_WCHAR, &wvarchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wvarchar_val[0], L'你'); + EXPECT_EQ(wvarchar_val[1], L'好'); + + // varchar + SQLCHAR varchar_val[4]; + buf_len = sizeof(SQLCHAR) * 4; + + ret = SQLGetData(this->stmt, 28, SQL_C_CHAR, &varchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(varchar_val[0], 'X'); + EXPECT_EQ(varchar_val[1], 'Y'); + EXPECT_EQ(varchar_val[2], 'Z'); + + // Date and Timestamp + + // Date + SQL_DATE_STRUCT date_var{}; + buf_len = sizeof(date_var); + + ret = SQLGetData(this->stmt, 29, SQL_C_TYPE_DATE, &date_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(date_var.day, 1); + EXPECT_EQ(date_var.month, 1); + EXPECT_EQ(date_var.year, 1400); + + ret = SQLGetData(this->stmt, 30, SQL_C_TYPE_DATE, &date_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(date_var.day, 31); + EXPECT_EQ(date_var.month, 12); + EXPECT_EQ(date_var.year, 9999); + + // Timestamp + SQL_TIMESTAMP_STRUCT timestamp_var{}; + buf_len = sizeof(timestamp_var); + + ret = SQLGetData(this->stmt, 31, SQL_C_TYPE_TIMESTAMP, ×tamp_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(timestamp_var.day, 1); + EXPECT_EQ(timestamp_var.month, 1); + EXPECT_EQ(timestamp_var.year, 1400); + EXPECT_EQ(timestamp_var.hour, 0); + EXPECT_EQ(timestamp_var.minute, 0); + EXPECT_EQ(timestamp_var.second, 0); + EXPECT_EQ(timestamp_var.fraction, 0); + + ret = SQLGetData(this->stmt, 32, SQL_C_TYPE_TIMESTAMP, ×tamp_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(timestamp_var.day, 31); + EXPECT_EQ(timestamp_var.month, 12); + EXPECT_EQ(timestamp_var.year, 9999); + EXPECT_EQ(timestamp_var.hour, 23); + EXPECT_EQ(timestamp_var.minute, 59); + EXPECT_EQ(timestamp_var.second, 59); + EXPECT_EQ(timestamp_var.fraction, 0); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectTimeQuery) { + // Mock server test is skipped due to limitation on the mock server. + // Time type from mock server does not include the fraction + this->connect(); + + std::wstring wsql = + LR"( + SELECT CAST(TIME '00:00:00' AS TIME) AS time_min, + CAST(TIME '23:59:59' AS TIME) AS time_max; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQL_TIME_STRUCT time_var{}; + SQLLEN buf_len = sizeof(time_var); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_TYPE_TIME, &time_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for time. + EXPECT_EQ(time_var.hour, 0); + EXPECT_EQ(time_var.minute, 0); + EXPECT_EQ(time_var.second, 0); + + ret = SQLGetData(this->stmt, 2, SQL_C_TYPE_TIME, &time_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for time. + EXPECT_EQ(time_var.hour, 23); + EXPECT_EQ(time_var.minute, 59); + EXPECT_EQ(time_var.second, 59); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLExecDirectVarbinaryQuery) { + // Have binary test on mock test base as remote test servers tend to have different + // formats for binary data + this->connect(); + + std::wstring wsql = L"SELECT X'ABCDEF' AS c_varbinary;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // varbinary + std::vector varbinary_val(3); + SQLLEN buf_len = varbinary_val.size(); + SQLLEN ind; + ret = SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val[0], buf_len, &ind); + EXPECT_EQ(varbinary_val[0], '\xAB'); + EXPECT_EQ(varbinary_val[1], '\xCD'); + EXPECT_EQ(varbinary_val[2], '\xEF'); + + this->disconnect(); +} + +// Tests with SQL_C_DEFAULT as the target type + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectDataQueryDefaultType) { + // Test with default types. Only testing target types supported by server. + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Numeric Types + // Signed Integer + SQLINTEGER slong_val; + SQLLEN buf_len = sizeof(slong_val); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 9, SQL_C_DEFAULT, &slong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 10, SQL_C_DEFAULT, &slong_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::max()); + + // Signed Big Int + SQLBIGINT sbig_int_val; + buf_len = sizeof(sbig_int_val); + + ret = SQLGetData(this->stmt, 13, SQL_C_DEFAULT, &sbig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 14, SQL_C_DEFAULT, &sbig_int_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::max()); + + // Decimal + SQL_NUMERIC_STRUCT decimal_val; + memset(&decimal_val, 0, sizeof(decimal_val)); + buf_len = sizeof(SQL_NUMERIC_STRUCT); + + ret = SQLGetData(this->stmt, 17, SQL_C_DEFAULT, &decimal_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for negative decimal_val value + EXPECT_EQ(decimal_val.sign, 0); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + memset(&decimal_val, 0, sizeof(decimal_val)); + ret = SQLGetData(this->stmt, 18, SQL_C_DEFAULT, &decimal_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for positive decimal_val value + EXPECT_EQ(decimal_val.sign, 1); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + // Float + float float_val; + buf_len = sizeof(float_val); + + ret = SQLGetData(this->stmt, 19, SQL_C_DEFAULT, &float_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative float value + EXPECT_EQ(float_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 20, SQL_C_DEFAULT, &float_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(float_val, std::numeric_limits::max()); + + // Double + SQLDOUBLE double_val; + buf_len = sizeof(double_val); + + ret = SQLGetData(this->stmt, 21, SQL_C_DEFAULT, &double_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative double value + EXPECT_EQ(double_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 22, SQL_C_DEFAULT, &double_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(double_val, std::numeric_limits::max()); + + // Bit + bool bit_val; + buf_len = sizeof(bit_val); + + ret = SQLGetData(this->stmt, 23, SQL_C_DEFAULT, &bit_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, false); + + ret = SQLGetData(this->stmt, 24, SQL_C_DEFAULT, &bit_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, true); + + // Characters + + // Char will be fetched as wchar by default + SQLWCHAR wchar_val[2]; + constexpr size_t wchar_size = driver::odbcabstraction::GetSqlWCharSize(); + buf_len = wchar_size * 2; + + ret = SQLGetData(this->stmt, 25, SQL_C_DEFAULT, &wchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wchar_val[0], L'Z'); + + // WChar + SQLWCHAR wchar_val2[2]; + buf_len = wchar_size * 2; + ret = SQLGetData(this->stmt, 26, SQL_C_DEFAULT, &wchar_val2, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wchar_val2[0], L'你'); + + // WVarchar + SQLWCHAR wvarchar_val[3]; + buf_len = wchar_size * 3; + + ret = SQLGetData(this->stmt, 27, SQL_C_DEFAULT, &wvarchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wvarchar_val[0], L'你'); + EXPECT_EQ(wvarchar_val[1], L'好'); + + // Varchar will be fetched as WVarchar by default + SQLWCHAR wvarchar_val2[4]; + buf_len = wchar_size * 4; + + ret = SQLGetData(this->stmt, 28, SQL_C_DEFAULT, &wvarchar_val2, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(wvarchar_val2[0], L'X'); + EXPECT_EQ(wvarchar_val2[1], L'Y'); + EXPECT_EQ(wvarchar_val2[2], L'Z'); + + // Date and Timestamp + + // Date + SQL_DATE_STRUCT date_var{}; + buf_len = sizeof(date_var); + + ret = SQLGetData(this->stmt, 29, SQL_C_DEFAULT, &date_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(date_var.day, 1); + EXPECT_EQ(date_var.month, 1); + EXPECT_EQ(date_var.year, 1400); + + ret = SQLGetData(this->stmt, 30, SQL_C_DEFAULT, &date_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(date_var.day, 31); + EXPECT_EQ(date_var.month, 12); + EXPECT_EQ(date_var.year, 9999); + + // Timestamp + SQL_TIMESTAMP_STRUCT timestamp_var{}; + buf_len = sizeof(timestamp_var); + + ret = SQLGetData(this->stmt, 31, SQL_C_DEFAULT, ×tamp_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(timestamp_var.day, 1); + EXPECT_EQ(timestamp_var.month, 1); + EXPECT_EQ(timestamp_var.year, 1400); + EXPECT_EQ(timestamp_var.hour, 0); + EXPECT_EQ(timestamp_var.minute, 0); + EXPECT_EQ(timestamp_var.second, 0); + EXPECT_EQ(timestamp_var.fraction, 0); + + ret = SQLGetData(this->stmt, 32, SQL_C_DEFAULT, ×tamp_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(timestamp_var.day, 31); + EXPECT_EQ(timestamp_var.month, 12); + EXPECT_EQ(timestamp_var.year, 9999); + EXPECT_EQ(timestamp_var.hour, 23); + EXPECT_EQ(timestamp_var.minute, 59); + EXPECT_EQ(timestamp_var.second, 59); + EXPECT_EQ(timestamp_var.fraction, 0); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectTimeQueryDefaultType) { + // Mock server test is skipped due to limitation on the mock server. + // Time type from mock server does not include the fraction + this->connect(); + + std::wstring wsql = + LR"( + SELECT CAST(TIME '00:00:00' AS TIME) AS time_min, + CAST(TIME '23:59:59' AS TIME) AS time_max; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQL_TIME_STRUCT time_var{}; + SQLLEN buf_len = sizeof(time_var); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_DEFAULT, &time_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for time. + EXPECT_EQ(time_var.hour, 0); + EXPECT_EQ(time_var.minute, 0); + EXPECT_EQ(time_var.second, 0); + + ret = SQLGetData(this->stmt, 2, SQL_C_DEFAULT, &time_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for time. + EXPECT_EQ(time_var.hour, 23); + EXPECT_EQ(time_var.minute, 59); + EXPECT_EQ(time_var.second, 59); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectVarbinaryQueryDefaultType) { + // Limitation on mock test server prevents SQL_C_DEFAULT from working properly. + // Mock server has type `DENSE_UNION` for varbinary. + // Note that not all remote servers support "from_hex" function + this->connect(); + + std::wstring wsql = L"SELECT from_hex('ABCDEF') AS c_varbinary;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // varbinary + std::vector varbinary_val(3); + SQLLEN buf_len = varbinary_val.size(); + SQLLEN ind; + ret = SQLGetData(this->stmt, 1, SQL_C_DEFAULT, &varbinary_val[0], buf_len, &ind); + EXPECT_EQ(varbinary_val[0], '\xAB'); + EXPECT_EQ(varbinary_val[1], '\xCD'); + EXPECT_EQ(varbinary_val[2], '\xEF'); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectGuidQueryUnsupported) { + this->connect(); + + // Query GUID as string as SQLite does not support GUID + std::wstring wsql = L"SELECT 'C77313CF-4E08-47CE-B6DF-94DD2FCF3541' AS guid;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLGUID guid_var; + SQLLEN buf_len = sizeof(guid_var); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_GUID, &guid_var, buf_len, &ind); + + EXPECT_EQ(ret, SQL_ERROR); + // GUID is not supported by ODBC + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY000); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectRowFetching) { + this->connect(); + + std::wstring wsql = + LR"( + SELECT 1 AS small_table + UNION ALL + SELECT 2 + UNION ALL + SELECT 3; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Fetch row 1 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + SQLLEN buf_len = sizeof(val); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify 1 is returned + EXPECT_EQ(val, 1); + + // Fetch row 2 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify 2 is returned + EXPECT_EQ(val, 2); + + // Fetch row 3 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify 3 is returned + EXPECT_EQ(val, 3); + + // Verify result set has no more data beyond row 3 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, &ind); + EXPECT_EQ(ret, SQL_ERROR); + + // Invalid cursor state + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_24000); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLFetchScrollRowFetching) { + this->connect(); + + SQLLEN rows_fetched; + SQLRETURN ret = SQLSetStmtAttr(this->stmt, SQL_ATTR_ROWS_FETCHED_PTR, &rows_fetched, 0); + + std::wstring wsql = + LR"( + SELECT 1 AS small_table + UNION ALL + SELECT 2 + UNION ALL + SELECT 3; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Fetch row 1 + ret = SQLFetchScroll(this->stmt, SQL_FETCH_NEXT, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + SQLLEN buf_len = sizeof(val); + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Verify 1 is returned + EXPECT_EQ(val, 1); + // Verify 1 row is fetched + EXPECT_EQ(rows_fetched, 1); + + // Fetch row 2 + ret = SQLFetchScroll(this->stmt, SQL_FETCH_NEXT, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify 2 is returned + EXPECT_EQ(val, 2); + // Verify 1 row is fetched in the last SQLFetchScroll call + EXPECT_EQ(rows_fetched, 1); + + // Fetch row 3 + ret = SQLFetchScroll(this->stmt, SQL_FETCH_NEXT, 0); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify 3 is returned + EXPECT_EQ(val, 3); + // Verify 1 row is fetched in the last SQLFetchScroll call + EXPECT_EQ(rows_fetched, 1); + + // Verify result set has no more data beyond row 3 + ret = SQLFetchScroll(this->stmt, SQL_FETCH_NEXT, 0); + EXPECT_EQ(ret, SQL_NO_DATA); + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, &ind); + + EXPECT_EQ(ret, SQL_ERROR); + // Invalid cursor state + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_24000); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLFetchScrollUnsupportedOrientation) { + // SQL_FETCH_PRIOR is the only supported fetch orientation. + this->connect(); + + std::wstring wsql = L"SELECT 1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetchScroll(this->stmt, SQL_FETCH_PRIOR, 0); + EXPECT_EQ(ret, SQL_ERROR); + + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HYC00); + + SQLLEN fetch_offset = 1; + + ret = SQLFetchScroll(this->stmt, SQL_FETCH_RELATIVE, fetch_offset); + EXPECT_EQ(ret, SQL_ERROR); + + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HYC00); + + ret = SQLFetchScroll(this->stmt, SQL_FETCH_ABSOLUTE, fetch_offset); + EXPECT_EQ(ret, SQL_ERROR); + + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HYC00); + + ret = SQLFetchScroll(this->stmt, SQL_FETCH_FIRST, 0); + EXPECT_EQ(ret, SQL_ERROR); + + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HYC00); + + ret = SQLFetchScroll(this->stmt, SQL_FETCH_LAST, 0); + EXPECT_EQ(ret, SQL_ERROR); + + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HYC00); + + ret = SQLFetchScroll(this->stmt, SQL_FETCH_BOOKMARK, fetch_offset); + EXPECT_EQ(ret, SQL_ERROR); + + // DM returns state HY106 for SQL_FETCH_BOOKMARK + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY106); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectVarcharTruncation) { + this->connect(); + + std::wstring wsql = L"SELECT 'VERY LONG STRING here' AS string_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + const int len = 17; + SQLCHAR char_val[len]; + SQLLEN buf_len = sizeof(SQLCHAR) * len; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + EXPECT_EQ(ODBC::SqlStringToString(char_val), std::string("VERY LONG STRING")); + EXPECT_EQ(ind, 21); + + // Fetch same column 2nd time + const int len2 = 2; + SQLCHAR char_val2[len2]; + buf_len = sizeof(SQLCHAR) * len2; + + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val2, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + EXPECT_EQ(ODBC::SqlStringToString(char_val2), std::string(" ")); + EXPECT_EQ(ind, 5); + + // Fetch same column 3rd time + const int len3 = 5; + SQLCHAR char_val3[len3]; + buf_len = sizeof(SQLCHAR) * len3; + + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val3, buf_len, &ind); + + // Verify that there is no more truncation reports. The full string has been fetched. + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(ODBC::SqlStringToString(char_val3), std::string("here")); + EXPECT_EQ(ind, 4); + + // Attempt to fetch data 4th time + SQLCHAR char_val4[len]; + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val4, 0, &ind); + // Verify SQL_NO_DATA is returned + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectWVarcharTruncation) { + this->connect(); + + std::wstring wsql = L"SELECT 'VERY LONG Unicode STRING 句子 here' AS wstring_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + const int len = 28; + SQLWCHAR wchar_val[len]; + constexpr size_t wchar_size = driver::odbcabstraction::GetSqlWCharSize(); + SQLLEN buf_len = wchar_size * len; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + EXPECT_EQ(std::wstring(wchar_val), std::wstring(L"VERY LONG Unicode STRING 句子")); + EXPECT_EQ(ind, 32 * wchar_size); + + // Fetch same column 2nd time + const int len2 = 2; + SQLWCHAR wchar_val2[len2]; + buf_len = wchar_size * len2; + + ret = SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val2, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + EXPECT_EQ(std::wstring(wchar_val2), std::wstring(L" ")); + EXPECT_EQ(ind, 5 * wchar_size); + + // Fetch same column 3rd time + const int len3 = 5; + SQLWCHAR wchar_val3[len3]; + buf_len = wchar_size * len3; + + ret = SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val3, buf_len, &ind); + + // Verify that there is no more truncation reports. The full string has been fetched. + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(std::wstring(wchar_val3), std::wstring(L"here")); + EXPECT_EQ(ind, 4 * wchar_size); + + // Attempt to fetch data 4th time + SQLWCHAR wchar_val4[len]; + ret = SQLGetData(this->stmt, 1, SQL_C_WCHAR, &wchar_val4, 0, &ind); + // Verify SQL_NO_DATA is returned + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLExecDirectVarbinaryTruncation) { + // Have binary test on mock test base as remote test servers tend to have different + // formats for binary data + this->connect(); + + std::wstring wsql = L"SELECT X'ABCDEFAB' AS c_varbinary;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // varbinary + std::vector varbinary_val(3); + SQLLEN buf_len = varbinary_val.size(); + SQLLEN ind; + ret = SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val[0], buf_len, &ind); + // Verify binary truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + EXPECT_EQ(varbinary_val[0], '\xAB'); + EXPECT_EQ(varbinary_val[1], '\xCD'); + EXPECT_EQ(varbinary_val[2], '\xEF'); + EXPECT_EQ(ind, 4); + + // Fetch same column 2nd time + std::vector varbinary_val2(1); + buf_len = varbinary_val2.size(); + + ret = SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val2[0], buf_len, &ind); + + // Verify that there is no more truncation reports. The full binary has been fetched. + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(varbinary_val[0], '\xAB'); + EXPECT_EQ(ind, 1); + + // Attempt to fetch data 3rd time + std::vector varbinary_val3(1); + buf_len = varbinary_val3.size(); + ret = SQLGetData(this->stmt, 1, SQL_C_BINARY, &varbinary_val3[0], buf_len, &ind); + // Verify SQL_NO_DATA is returned + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectFloatTruncation) { + // Test is disabled until float truncation is supported. + // GH-46985: return warning message instead of error on float truncation case + GTEST_SKIP(); + this->connect(); + + std::wstring wsql; + if constexpr (std::is_same_v) { + wsql = std::wstring(L"SELECT CAST(1.234 AS REAL) AS float_val"); + } else if constexpr (std::is_same_v) { + wsql = std::wstring(L"SELECT CAST(1.234 AS FLOAT) AS float_val"); + } + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + int16_t ssmall_int_val; + + ret = SQLGetData(this->stmt, 1, SQL_C_SSHORT, &ssmall_int_val, 0, 0); + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify float truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01S07); + + EXPECT_EQ(ssmall_int_val, 1); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectNullQuery) { + // Limitation on mock test server prevents null from working properly, so use remote + // server instead. Mock server has type `DENSE_UNION` for null column data. + this->connect(); + + std::wstring wsql = L"SELECT null as null_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify SQL_NULL_DATA is returned for indicator + EXPECT_EQ(ind, SQL_NULL_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLExecDirectTruncationQueryNullIndicator) { + // Driver should not error out when indicator is null if the cell is non-null + // Have binary test on mock test base as remote test servers tend to have different + // formats for binary data + this->connect(); + + std::wstring wsql = + LR"( + SELECT 1, + 'VERY LONG STRING here' AS string_col, + 'VERY LONG Unicode STRING 句子 here' AS wstring_col, + X'ABCDEFAB' AS c_varbinary; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Verify 1 is returned for non-truncation case. + EXPECT_EQ(val, 1); + + // Char + const int len = 17; + SQLCHAR char_val[len]; + SQLLEN buf_len = sizeof(SQLCHAR) * len; + + ret = SQLGetData(this->stmt, 2, SQL_C_CHAR, &char_val, buf_len, 0); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + // WChar + const int len2 = 28; + SQLWCHAR wchar_val[len2]; + constexpr size_t wchar_size = driver::odbcabstraction::GetSqlWCharSize(); + buf_len = wchar_size * len2; + + ret = SQLGetData(this->stmt, 3, SQL_C_WCHAR, &wchar_val, buf_len, 0); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + // Verify string truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + // varbinary + std::vector varbinary_val(3); + buf_len = varbinary_val.size(); + ret = SQLGetData(this->stmt, 4, SQL_C_BINARY, &varbinary_val[0], buf_len, 0); + // Verify binary truncation is reported + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_01004); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExecDirectNullQueryNullIndicator) { + // Limitation on mock test server prevents null from working properly, so use remote + // server instead. Mock server has type `DENSE_UNION` for null column data. + this->connect(); + + std::wstring wsql = L"SELECT null as null_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLINTEGER val; + + ret = SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + // Verify invalid null indicator is reported, as it is required + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_22002); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExecDirectIgnoreInvalidBufLen) { + // Verify the driver ignores invalid buffer length for fixed data types + this->connect(); + + std::wstring wsql = this->getQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Numeric Types + + // Signed Tiny Int + int8_t stiny_int_val; + SQLLEN invalid_buf_len = -1; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_STINYINT, &stiny_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(stiny_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 2, SQL_C_STINYINT, &stiny_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(stiny_int_val, std::numeric_limits::max()); + + // Unsigned Tiny Int + uint8_t utiny_int_val; + + ret = SQLGetData(this->stmt, 3, SQL_C_UTINYINT, &utiny_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(utiny_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 4, SQL_C_UTINYINT, &utiny_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(utiny_int_val, std::numeric_limits::max()); + + // Signed Small Int + int16_t ssmall_int_val; + + ret = SQLGetData(this->stmt, 5, SQL_C_SSHORT, &ssmall_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ssmall_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 6, SQL_C_SSHORT, &ssmall_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ssmall_int_val, std::numeric_limits::max()); + + // Unsigned Small Int + uint16_t usmall_int_val; + + ret = SQLGetData(this->stmt, 7, SQL_C_USHORT, &usmall_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(usmall_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 8, SQL_C_USHORT, &usmall_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(usmall_int_val, std::numeric_limits::max()); + + // Signed Integer + SQLINTEGER slong_val; + + ret = SQLGetData(this->stmt, 9, SQL_C_SLONG, &slong_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 10, SQL_C_SLONG, &slong_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(slong_val, std::numeric_limits::max()); + + // Unsigned Integer + SQLUINTEGER ulong_val; + + ret = SQLGetData(this->stmt, 11, SQL_C_ULONG, &ulong_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ulong_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 12, SQL_C_ULONG, &ulong_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ulong_val, std::numeric_limits::max()); + + // Signed Big Int + SQLBIGINT sbig_int_val; + + ret = SQLGetData(this->stmt, 13, SQL_C_SBIGINT, &sbig_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 14, SQL_C_SBIGINT, &sbig_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(sbig_int_val, std::numeric_limits::max()); + + // Unsigned Big Int + SQLUBIGINT ubig_int_val; + + ret = SQLGetData(this->stmt, 15, SQL_C_UBIGINT, &ubig_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ubig_int_val, std::numeric_limits::min()); + + ret = SQLGetData(this->stmt, 16, SQL_C_UBIGINT, &ubig_int_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(ubig_int_val, std::numeric_limits::max()); + + // Decimal + SQL_NUMERIC_STRUCT decimal_val; + memset(&decimal_val, 0, sizeof(decimal_val)); + + ret = SQLGetData(this->stmt, 17, SQL_C_NUMERIC, &decimal_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for negative decimal_val value + EXPECT_EQ(decimal_val.sign, 0); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + memset(&decimal_val, 0, sizeof(decimal_val)); + ret = SQLGetData(this->stmt, 18, SQL_C_NUMERIC, &decimal_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check for positive decimal_val value + EXPECT_EQ(decimal_val.sign, 1); + EXPECT_EQ(decimal_val.scale, 0); + EXPECT_EQ(decimal_val.precision, 38); + EXPECT_THAT(decimal_val.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + // Float + float float_val; + + ret = SQLGetData(this->stmt, 19, SQL_C_FLOAT, &float_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative float value + EXPECT_EQ(float_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 20, SQL_C_FLOAT, &float_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(float_val, std::numeric_limits::max()); + + // Double + SQLDOUBLE double_val; + + ret = SQLGetData(this->stmt, 21, SQL_C_DOUBLE, &double_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Get minimum negative double value + EXPECT_EQ(double_val, -std::numeric_limits::max()); + + ret = SQLGetData(this->stmt, 22, SQL_C_DOUBLE, &double_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(double_val, std::numeric_limits::max()); + + // Bit + bool bit_val; + + ret = SQLGetData(this->stmt, 23, SQL_C_BIT, &bit_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, false); + + ret = SQLGetData(this->stmt, 24, SQL_C_BIT, &bit_val, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(bit_val, true); + + // Date and Timestamp + + // Date + SQL_DATE_STRUCT date_var{}; + + ret = SQLGetData(this->stmt, 29, SQL_C_TYPE_DATE, &date_var, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(date_var.day, 1); + EXPECT_EQ(date_var.month, 1); + EXPECT_EQ(date_var.year, 1400); + + ret = SQLGetData(this->stmt, 30, SQL_C_TYPE_DATE, &date_var, invalid_buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(date_var.day, 31); + EXPECT_EQ(date_var.month, 12); + EXPECT_EQ(date_var.year, 9999); + + // Timestamp + SQL_TIMESTAMP_STRUCT timestamp_var{}; + + ret = SQLGetData(this->stmt, 31, SQL_C_TYPE_TIMESTAMP, ×tamp_var, invalid_buf_len, + &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check min values for date. Min valid year is 1400. + EXPECT_EQ(timestamp_var.day, 1); + EXPECT_EQ(timestamp_var.month, 1); + EXPECT_EQ(timestamp_var.year, 1400); + EXPECT_EQ(timestamp_var.hour, 0); + EXPECT_EQ(timestamp_var.minute, 0); + EXPECT_EQ(timestamp_var.second, 0); + EXPECT_EQ(timestamp_var.fraction, 0); + + ret = SQLGetData(this->stmt, 32, SQL_C_TYPE_TIMESTAMP, ×tamp_var, invalid_buf_len, + &ind); + + EXPECT_EQ(ret, SQL_SUCCESS); + // Check max values for date. Max valid year is 9999. + EXPECT_EQ(timestamp_var.day, 31); + EXPECT_EQ(timestamp_var.month, 12); + EXPECT_EQ(timestamp_var.year, 9999); + EXPECT_EQ(timestamp_var.hour, 23); + EXPECT_EQ(timestamp_var.minute, 59); + EXPECT_EQ(timestamp_var.second, 59); + EXPECT_EQ(timestamp_var.fraction, 0); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLBindColDataQuery) { + this->connect(); + + // Numeric Types + + // Signed Tiny Int + int8_t stiny_int_val_min; + int8_t stiny_int_val_max; + SQLLEN buf_len = 0; + SQLLEN ind; + + SQLRETURN ret = + SQLBindCol(this->stmt, 1, SQL_C_STINYINT, &stiny_int_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 2, SQL_C_STINYINT, &stiny_int_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Unsigned Tiny Int + uint8_t utiny_int_val_min; + uint8_t utiny_int_val_max; + + ret = SQLBindCol(this->stmt, 3, SQL_C_UTINYINT, &utiny_int_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 4, SQL_C_UTINYINT, &utiny_int_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Signed Small Int + int16_t ssmall_int_val_min; + int16_t ssmall_int_val_max; + + ret = SQLBindCol(this->stmt, 5, SQL_C_SSHORT, &ssmall_int_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 6, SQL_C_SSHORT, &ssmall_int_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Unsigned Small Int + uint16_t usmall_int_val_min; + uint16_t usmall_int_val_max; + + ret = SQLBindCol(this->stmt, 7, SQL_C_USHORT, &usmall_int_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 8, SQL_C_USHORT, &usmall_int_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Signed Integer + SQLINTEGER slong_val_min; + SQLINTEGER slong_val_max; + + ret = SQLBindCol(this->stmt, 9, SQL_C_SLONG, &slong_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 10, SQL_C_SLONG, &slong_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Unsigned Integer + SQLUINTEGER ulong_val_min; + SQLUINTEGER ulong_val_max; + + ret = SQLBindCol(this->stmt, 11, SQL_C_ULONG, &ulong_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 12, SQL_C_ULONG, &ulong_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Signed Big Int + SQLBIGINT sbig_int_val_min; + SQLBIGINT sbig_int_val_max; + + ret = SQLBindCol(this->stmt, 13, SQL_C_SBIGINT, &sbig_int_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 14, SQL_C_SBIGINT, &sbig_int_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Unsigned Big Int + SQLUBIGINT ubig_int_val_min; + SQLUBIGINT ubig_int_val_max; + + ret = SQLBindCol(this->stmt, 15, SQL_C_UBIGINT, &ubig_int_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 16, SQL_C_UBIGINT, &ubig_int_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Decimal + SQL_NUMERIC_STRUCT decimal_val_neg; + SQL_NUMERIC_STRUCT decimal_val_pos; + memset(&decimal_val_neg, 0, sizeof(decimal_val_neg)); + memset(&decimal_val_pos, 0, sizeof(decimal_val_pos)); + + ret = SQLBindCol(this->stmt, 17, SQL_C_NUMERIC, &decimal_val_neg, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 18, SQL_C_NUMERIC, &decimal_val_pos, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Float + float float_val_min; + float float_val_max; + + ret = SQLBindCol(this->stmt, 19, SQL_C_FLOAT, &float_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 20, SQL_C_FLOAT, &float_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Double + SQLDOUBLE double_val_min; + SQLDOUBLE double_val_max; + + ret = SQLBindCol(this->stmt, 21, SQL_C_DOUBLE, &double_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 22, SQL_C_DOUBLE, &double_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Bit + bool bit_val_false; + bool bit_val_true; + + ret = SQLBindCol(this->stmt, 23, SQL_C_BIT, &bit_val_false, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 24, SQL_C_BIT, &bit_val_true, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Characters + SQLCHAR char_val[2]; + buf_len = sizeof(SQLCHAR) * 2; + + ret = SQLBindCol(this->stmt, 25, SQL_C_CHAR, &char_val, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLWCHAR wchar_val[2]; + constexpr size_t wchar_size = driver::odbcabstraction::GetSqlWCharSize(); + buf_len = wchar_size * 2; + + ret = SQLBindCol(this->stmt, 26, SQL_C_WCHAR, &wchar_val, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLWCHAR wvarchar_val[3]; + buf_len = wchar_size * 3; + + ret = SQLBindCol(this->stmt, 27, SQL_C_WCHAR, &wvarchar_val, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLCHAR varchar_val[4]; + buf_len = sizeof(SQLCHAR) * 4; + + ret = SQLBindCol(this->stmt, 28, SQL_C_CHAR, &varchar_val, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Date and Timestamp + SQL_DATE_STRUCT date_val_min{}, date_val_max{}; + buf_len = 0; + + ret = SQLBindCol(this->stmt, 29, SQL_C_TYPE_DATE, &date_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 30, SQL_C_TYPE_DATE, &date_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQL_TIMESTAMP_STRUCT timestamp_val_min{}, timestamp_val_max{}; + + ret = + SQLBindCol(this->stmt, 31, SQL_C_TYPE_TIMESTAMP, ×tamp_val_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = + SQLBindCol(this->stmt, 32, SQL_C_TYPE_TIMESTAMP, ×tamp_val_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Execute query and fetch data once since there is only 1 row. + std::wstring wsql = this->getQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Data verification + + // Signed Tiny Int + EXPECT_EQ(stiny_int_val_min, std::numeric_limits::min()); + EXPECT_EQ(stiny_int_val_max, std::numeric_limits::max()); + + // Unsigned Tiny Int + EXPECT_EQ(utiny_int_val_min, std::numeric_limits::min()); + EXPECT_EQ(utiny_int_val_max, std::numeric_limits::max()); + + // Signed Small Int + EXPECT_EQ(ssmall_int_val_min, std::numeric_limits::min()); + EXPECT_EQ(ssmall_int_val_max, std::numeric_limits::max()); + + // Unsigned Small Int + EXPECT_EQ(usmall_int_val_min, std::numeric_limits::min()); + EXPECT_EQ(usmall_int_val_max, std::numeric_limits::max()); + + // Signed Long + EXPECT_EQ(slong_val_min, std::numeric_limits::min()); + EXPECT_EQ(slong_val_max, std::numeric_limits::max()); + + // Unsigned Long + EXPECT_EQ(ulong_val_min, std::numeric_limits::min()); + EXPECT_EQ(ulong_val_max, std::numeric_limits::max()); + + // Signed Big Int + EXPECT_EQ(sbig_int_val_min, std::numeric_limits::min()); + EXPECT_EQ(sbig_int_val_max, std::numeric_limits::max()); + + // Unsigned Big Int + EXPECT_EQ(ubig_int_val_min, std::numeric_limits::min()); + EXPECT_EQ(ubig_int_val_max, std::numeric_limits::max()); + + // Decimal + EXPECT_EQ(decimal_val_neg.sign, 0); + EXPECT_EQ(decimal_val_neg.scale, 0); + EXPECT_EQ(decimal_val_neg.precision, 38); + EXPECT_THAT(decimal_val_neg.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0)); + + EXPECT_EQ(decimal_val_pos.sign, 1); + EXPECT_EQ(decimal_val_pos.scale, 0); + EXPECT_EQ(decimal_val_pos.precision, 38); + EXPECT_THAT(decimal_val_pos.val, ::testing::ElementsAre(0xFF, 0xC9, 0x9A, 0x3B, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0)); + + // Float + EXPECT_EQ(float_val_min, -std::numeric_limits::max()); + EXPECT_EQ(float_val_max, std::numeric_limits::max()); + + // Double + EXPECT_EQ(double_val_min, -std::numeric_limits::max()); + EXPECT_EQ(double_val_max, std::numeric_limits::max()); + + // Bit + EXPECT_EQ(bit_val_false, false); + EXPECT_EQ(bit_val_true, true); + + // Characters + EXPECT_EQ(char_val[0], 'Z'); + EXPECT_EQ(wchar_val[0], L'你'); + EXPECT_EQ(wvarchar_val[0], L'你'); + EXPECT_EQ(wvarchar_val[1], L'好'); + + EXPECT_EQ(varchar_val[0], 'X'); + EXPECT_EQ(varchar_val[1], 'Y'); + EXPECT_EQ(varchar_val[2], 'Z'); + + // Date + EXPECT_EQ(date_val_min.day, 1); + EXPECT_EQ(date_val_min.month, 1); + EXPECT_EQ(date_val_min.year, 1400); + + EXPECT_EQ(date_val_max.day, 31); + EXPECT_EQ(date_val_max.month, 12); + EXPECT_EQ(date_val_max.year, 9999); + + // Timestamp + EXPECT_EQ(timestamp_val_min.day, 1); + EXPECT_EQ(timestamp_val_min.month, 1); + EXPECT_EQ(timestamp_val_min.year, 1400); + EXPECT_EQ(timestamp_val_min.hour, 0); + EXPECT_EQ(timestamp_val_min.minute, 0); + EXPECT_EQ(timestamp_val_min.second, 0); + EXPECT_EQ(timestamp_val_min.fraction, 0); + + EXPECT_EQ(timestamp_val_max.day, 31); + EXPECT_EQ(timestamp_val_max.month, 12); + EXPECT_EQ(timestamp_val_max.year, 9999); + EXPECT_EQ(timestamp_val_max.hour, 23); + EXPECT_EQ(timestamp_val_max.minute, 59); + EXPECT_EQ(timestamp_val_max.second, 59); + EXPECT_EQ(timestamp_val_max.fraction, 0); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLBindColTimeQuery) { + // Mock server test is skipped due to limitation on the mock server. + // Time type from mock server does not include the fraction + this->connect(); + + SQL_TIME_STRUCT time_var_min{}; + SQL_TIME_STRUCT time_var_max{}; + SQLLEN buf_len = sizeof(time_var_min); + SQLLEN ind; + + SQLRETURN ret = + SQLBindCol(this->stmt, 1, SQL_C_TYPE_TIME, &time_var_min, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLBindCol(this->stmt, 2, SQL_C_TYPE_TIME, &time_var_max, buf_len, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring wsql = + LR"( + SELECT CAST(TIME '00:00:00' AS TIME) AS time_min, + CAST(TIME '23:59:59' AS TIME) AS time_max; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check min values for time. + EXPECT_EQ(time_var_min.hour, 0); + EXPECT_EQ(time_var_min.minute, 0); + EXPECT_EQ(time_var_min.second, 0); + + // Check max values for time. + EXPECT_EQ(time_var_max.hour, 23); + EXPECT_EQ(time_var_max.minute, 59); + EXPECT_EQ(time_var_max.second, 59); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, TestSQLBindColVarbinaryQuery) { + // Have binary test on mock test base as remote test servers tend to have different + // formats for binary data + this->connect(); + + // varbinary + std::vector varbinary_val(3); + SQLLEN buf_len = varbinary_val.size(); + SQLLEN ind; + SQLRETURN ret = + SQLBindCol(this->stmt, 1, SQL_C_BINARY, &varbinary_val[0], buf_len, &ind); + + std::wstring wsql = L"SELECT X'ABCDEF' AS c_varbinary;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Check varbinary values + EXPECT_EQ(varbinary_val[0], '\xAB'); + EXPECT_EQ(varbinary_val[1], '\xCD'); + EXPECT_EQ(varbinary_val[2], '\xEF'); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLBindColNullQuery) { + // Limitation on mock test server prevents null from working properly, so use remote + // server instead. Mock server has type `DENSE_UNION` for null column data. + this->connect(); + + SQLINTEGER val; + SQLLEN ind; + + SQLRETURN ret = SQLBindCol(this->stmt, 1, SQL_C_LONG, &val, 0, &ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + std::wstring wsql = L"SELECT null as null_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify SQL_NULL_DATA is returned for indicator + EXPECT_EQ(ind, SQL_NULL_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLBindColNullQueryNullIndicator) { + // Limitation on mock test server prevents null from working properly, so use remote + // server instead. Mock server has type `DENSE_UNION` for null column data. + this->connect(); + + SQLINTEGER val; + + SQLRETURN ret = SQLBindCol(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + std::wstring wsql = L"SELECT null as null_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + + EXPECT_EQ(ret, SQL_ERROR); + // Verify invalid null indicator is reported, as it is required + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_22002); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLBindColRowFetching) { + this->connect(); + + SQLINTEGER val; + SQLLEN buf_len = sizeof(val); + SQLLEN ind; + + // Same variable will be used for column 1, the value of `val` + // should be updated after every SQLFetch call. + SQLRETURN ret = SQLBindCol(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind); + + std::wstring wsql = + LR"( + SELECT 1 AS small_table + UNION ALL + SELECT 2 + UNION ALL + SELECT 3; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Fetch row 1 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify 1 is returned + EXPECT_EQ(val, 1); + + // Fetch row 2 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify 2 is returned + EXPECT_EQ(val, 2); + + // Fetch row 3 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify 3 is returned + EXPECT_EQ(val, 3); + + // Verify result set has no more data beyond row 3 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLBindColRowArraySize) { + // Set SQL_ATTR_ROW_ARRAY_SIZE to fetch 3 rows at once + this->connect(); + + constexpr SQLULEN rows = 3; + SQLINTEGER val[rows]; + SQLLEN buf_len = sizeof(val); + SQLLEN ind[rows]; + + // Same variable will be used for column 1, the value of `val` + // should be updated after every SQLFetch call. + SQLRETURN ret = SQLBindCol(this->stmt, 1, SQL_C_LONG, val, buf_len, ind); + + SQLLEN rows_fetched; + ret = SQLSetStmtAttr(this->stmt, SQL_ATTR_ROWS_FETCHED_PTR, &rows_fetched, 0); + + std::wstring wsql = + LR"( + SELECT 1 AS small_table + UNION ALL + SELECT 2 + UNION ALL + SELECT 3; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLSetStmtAttr(this->stmt, SQL_ATTR_ROW_ARRAY_SIZE, + reinterpret_cast(rows), 0); + + // Fetch 3 rows at once + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify 3 rows are fetched + EXPECT_EQ(rows_fetched, 3); + + // Verify 1 is returned + EXPECT_EQ(val[0], 1); + // Verify 2 is returned + EXPECT_EQ(val[1], 2); + // Verify 3 is returned + EXPECT_EQ(val[2], 3); + + // Verify result set has no more data beyond row 3 + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLBindColIndicatorOnly) { + // GH-47021: implement driver to return indicator value when data pointer is null + GTEST_SKIP(); + // Verify driver supports null data pointer with valid indicator pointer + this->connect(); + + // Numeric Types + + // Signed Tiny Int + SQLLEN stiny_int_ind; + + SQLRETURN ret = SQLBindCol(this->stmt, 1, SQL_C_STINYINT, 0, 0, &stiny_int_ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Characters + SQLLEN buf_len = sizeof(SQLCHAR) * 2; + SQLLEN char_val_ind; + + ret = SQLBindCol(this->stmt, 25, SQL_C_CHAR, 0, buf_len, &char_val_ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Execute query and fetch data once since there is only 1 row. + std::wstring wsql = this->getQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Verify values for indicator pointer + // Signed Tiny Int + EXPECT_EQ(stiny_int_ind, 1); + + // Char array + EXPECT_EQ(char_val_ind, 1); + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLBindColIndicatorOnlySQLUnbind) { + // Verify driver supports valid indicator pointer after unbinding all columns + this->connect(); + + // Numeric Types + + // Signed Tiny Int + int8_t stiny_int_val; + SQLLEN stiny_int_ind; + + SQLRETURN ret = + SQLBindCol(this->stmt, 1, SQL_C_STINYINT, &stiny_int_val, 0, &stiny_int_ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Characters + SQLCHAR char_val[2]; + SQLLEN buf_len = sizeof(SQLCHAR) * 2; + SQLLEN char_val_ind; + + ret = SQLBindCol(this->stmt, 25, SQL_C_CHAR, &char_val, buf_len, &char_val_ind); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Driver should still be able to execute queries after unbinding columns + ret = SQLFreeStmt(this->stmt, SQL_UNBIND); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Execute query and fetch data once since there is only 1 row. + std::wstring wsql = this->getQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + // GH-47021: implement driver to return indicator value when data pointer is null and + // uncomment the checks Verify values for indicator pointer Signed Tiny Int + // EXPECT_EQ(stiny_int_ind, 1); + + // Char array + // EXPECT_EQ(char_val_ind, 1); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLExtendedFetchRowFetching) { + // Set SQL_ROWSET_SIZE to fetch 3 rows at once + this->connect(); + + constexpr SQLULEN rows = 3; + SQLINTEGER val[rows]; + SQLLEN buf_len = sizeof(val); + SQLLEN ind[rows]; + + // Same variable will be used for column 1, the value of `val` + // should be updated after every SQLFetch call. + SQLRETURN ret = SQLBindCol(this->stmt, 1, SQL_C_LONG, val, buf_len, ind); + + ret = + SQLSetStmtAttr(this->stmt, SQL_ROWSET_SIZE, reinterpret_cast(rows), 0); + + std::wstring wsql = + LR"( + SELECT 1 AS small_table + UNION ALL + SELECT 2 + UNION ALL + SELECT 3; + )"; + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + // Fetch row 1-3. + SQLULEN row_count; + SQLUSMALLINT row_status[rows]; + + ret = SQLExtendedFetch(this->stmt, SQL_FETCH_NEXT, 0, &row_count, row_status); + EXPECT_EQ(ret, SQL_SUCCESS); + EXPECT_EQ(row_count, 3); + + for (int i = 0; i < rows; i++) { + EXPECT_EQ(row_status[i], SQL_SUCCESS); + } + + // Verify 1 is returned for row 1 + EXPECT_EQ(val[0], 1); + // Verify 2 is returned for row 2 + EXPECT_EQ(val[1], 2); + // Verify 3 is returned for row 3 + EXPECT_EQ(val[2], 3); + + // Verify result set has no more data beyond row 3 + SQLULEN row_count2; + SQLUSMALLINT row_status2[rows]; + ret = SQLExtendedFetch(this->stmt, SQL_FETCH_NEXT, 0, &row_count2, row_status2); + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, TestSQLExtendedFetchQueryNullIndicator) { + // GH-47110: SQLExtendedFetch should return SQL_SUCCESS_WITH_INFO for 22002 + // Limitation on mock test server prevents null from working properly, so use remote + // server instead. Mock server has type `DENSE_UNION` for null column data. + GTEST_SKIP(); + this->connect(); + + SQLINTEGER val; + + SQLRETURN ret = SQLBindCol(this->stmt, 1, SQL_C_LONG, &val, 0, 0); + + std::wstring wsql = L"SELECT null as null_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ret = SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + SQLULEN row_count1; + SQLUSMALLINT row_status1[1]; + + // SQLExtendedFetch should return SQL_SUCCESS_WITH_INFO for 22002 state + ret = SQLExtendedFetch(this->stmt, SQL_FETCH_NEXT, 0, &row_count1, row_status1); + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_22002); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLMoreResultsNoData) { + // Verify SQLMoreResults is stubbed to return SQL_NO_DATA + this->connect(); + + std::wstring wsql = L"SELECT 1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLMoreResults(this->stmt); + + EXPECT_EQ(ret, SQL_NO_DATA); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLMoreResultsInvalidFunctionSequence) { + this->connect(); + + SQLRETURN ret = SQLMoreResults(this->stmt); + + // Verify function sequence error state is reported when SQLMoreResults is called + // without executing any queries + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY010); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsInputString) { + this->connect(); + + SQLWCHAR buf[1024]; + constexpr SQLINTEGER bufCharLen = sizeof(buf) / ODBC::GetSqlWCharSize(); + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + std::wstring expectedString = std::wstring(inputStr); + + SQLRETURN ret = + SQLNativeSql(this->conn, inputStr, inputCharLen, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(outputCharLen, inputCharLen); + + // returned length is in characters + std::wstring returnedString(buf, buf + outputCharLen); + + EXPECT_EQ(returnedString, expectedString); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsNTSInputString) { + this->connect(); + + SQLWCHAR buf[1024]; + constexpr SQLINTEGER bufCharLen = sizeof(buf) / ODBC::GetSqlWCharSize(); + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + std::wstring expectedString = std::wstring(inputStr); + + SQLRETURN ret = + SQLNativeSql(this->conn, inputStr, SQL_NTS, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(outputCharLen, inputCharLen); + + // returned length is in characters + std::wstring returnedString(buf, buf + outputCharLen); + + EXPECT_EQ(returnedString, expectedString); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsInputStringLength) { + this->connect(); + + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + std::wstring expectedString = std::wstring(inputStr); + + SQLRETURN ret = + SQLNativeSql(this->conn, inputStr, inputCharLen, nullptr, 0, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(outputCharLen, inputCharLen); + + ret = SQLNativeSql(this->conn, inputStr, SQL_NTS, nullptr, 0, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(outputCharLen, inputCharLen); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsTruncatedString) { + this->connect(); + + const SQLINTEGER smallBufSizeInChar = 11; + SQLWCHAR smallBuf[smallBufSizeInChar]; + constexpr SQLINTEGER smallBufCharLen = sizeof(smallBuf) / ODBC::GetSqlWCharSize(); + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + + // Create expected return string based on buf size + SQLWCHAR expectedStringBuf[smallBufSizeInChar]; + wcsncpy(expectedStringBuf, inputStr, 10); + expectedStringBuf[10] = L'\0'; + std::wstring expectedString(expectedStringBuf, expectedStringBuf + smallBufSizeInChar); + + SQLRETURN ret = SQLNativeSql(this->conn, inputStr, inputCharLen, smallBuf, + smallBufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_01004); + + // Returned text length represents full string char length regardless of truncation + EXPECT_EQ(outputCharLen, inputCharLen); + + std::wstring returnedString(smallBuf, smallBuf + smallBufCharLen); + + EXPECT_EQ(returnedString, expectedString); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsErrorOnBadInputs) { + this->connect(); + + SQLWCHAR buf[1024]; + constexpr SQLINTEGER bufCharLen = sizeof(buf) / ODBC::GetSqlWCharSize(); + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + + SQLRETURN ret = + SQLNativeSql(this->conn, nullptr, inputCharLen, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY009); + + ret = SQLNativeSql(this->conn, nullptr, SQL_NTS, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY009); + + ret = SQLNativeSql(this->conn, inputStr, -100, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY090); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLNumResultColsReturnsColumnsOnSelect) { + this->connect(); + + SQLSMALLINT columnCount = 0; + SQLSMALLINT expectedValue = 3; + SQLWCHAR sqlQuery[] = L"SELECT 1 AS col1, 'One' AS col2, 3 AS col3"; + SQLINTEGER queryLength = static_cast(wcslen(sqlQuery)); + + SQLRETURN ret = SQLExecDirect(this->stmt, sqlQuery, queryLength); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + + EXPECT_EQ(ret, SQL_SUCCESS); + + CheckIntColumn(this->stmt, 1, 1); + CheckStringColumnW(this->stmt, 2, L"One"); + CheckIntColumn(this->stmt, 3, 3); + + ret = SQLNumResultCols(this->stmt, &columnCount); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(columnCount, expectedValue); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLNumResultColsReturnsSuccessOnNullptr) { + this->connect(); + + SQLWCHAR sqlQuery[] = L"SELECT 1 AS col1, 'One' AS col2, 3 AS col3"; + SQLINTEGER queryLength = static_cast(wcslen(sqlQuery)); + + SQLRETURN ret = SQLExecDirect(this->stmt, sqlQuery, queryLength); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + + EXPECT_EQ(ret, SQL_SUCCESS); + + CheckIntColumn(this->stmt, 1, 1); + CheckStringColumnW(this->stmt, 2, L"One"); + CheckIntColumn(this->stmt, 3, 3); + + ret = SQLNumResultCols(this->stmt, nullptr); + + EXPECT_EQ(ret, SQL_SUCCESS); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLNumResultColsFunctionSequenceErrorOnNoQuery) { + this->connect(); + + SQLSMALLINT columnCount = 0; + SQLSMALLINT expectedValue = 0; + + SQLRETURN ret = SQLNumResultCols(this->stmt, &columnCount); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY010); + + EXPECT_EQ(columnCount, expectedValue); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLRowCountReturnsNegativeOneOnSelect) { + this->connect(); + + SQLLEN rowCount = 0; + SQLLEN expectedValue = -1; + SQLWCHAR sqlQuery[] = L"SELECT 1 AS col1, 'One' AS col2, 3 AS col3"; + SQLINTEGER queryLength = static_cast(wcslen(sqlQuery)); + + SQLRETURN ret = SQLExecDirect(this->stmt, sqlQuery, queryLength); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + + EXPECT_EQ(ret, SQL_SUCCESS); + + CheckIntColumn(this->stmt, 1, 1); + CheckStringColumnW(this->stmt, 2, L"One"); + CheckIntColumn(this->stmt, 3, 3); + + ret = SQLRowCount(this->stmt, &rowCount); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(rowCount, expectedValue); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLRowCountReturnsSuccessOnNullptr) { + this->connect(); + + SQLWCHAR sqlQuery[] = L"SELECT 1 AS col1, 'One' AS col2, 3 AS col3"; + SQLINTEGER queryLength = static_cast(wcslen(sqlQuery)); + + SQLRETURN ret = SQLExecDirect(this->stmt, sqlQuery, queryLength); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + + EXPECT_EQ(ret, SQL_SUCCESS); + + CheckIntColumn(this->stmt, 1, 1); + CheckStringColumnW(this->stmt, 2, L"One"); + CheckIntColumn(this->stmt, 3, 3); + + ret = SQLRowCount(this->stmt, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, SQLRowCountFunctionSequenceErrorOnNoQuery) { + this->connect(); + + SQLLEN rowCount = 0; + SQLLEN expectedValue = 0; + + SQLRETURN ret = SQLRowCount(this->stmt, &rowCount); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_HY010); + + EXPECT_EQ(rowCount, expectedValue); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLFreeStmtSQLClose) { + this->connect(); + + std::wstring wsql = L"SELECT 1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFreeStmt(this->stmt, SQL_CLOSE); + + EXPECT_EQ(ret, SQL_SUCCESS); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLCloseCursor) { + this->connect(); + + std::wstring wsql = L"SELECT 1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLCloseCursor(this->stmt); + + EXPECT_EQ(ret, SQL_SUCCESS); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLFreeStmtSQLCloseWithoutCursor) { + // SQLFreeStmt(SQL_CLOSE) does not throw error with invalid cursor + this->connect(); + + SQLRETURN ret = SQLFreeStmt(this->stmt, SQL_CLOSE); + + EXPECT_EQ(ret, SQL_SUCCESS); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLCloseCursorWithoutCursor) { + this->connect(); + + SQLRETURN ret = SQLCloseCursor(this->stmt); + + EXPECT_EQ(ret, SQL_ERROR); + + // Verify invalid cursor error state is returned + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, error_state_24000); + + this->disconnect(); +} + +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc new file mode 100644 index 000000000000..d2e7aab7d451 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc @@ -0,0 +1,584 @@ +// 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" + +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h" +#include "arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/statement.h" + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace arrow::flight::sql::odbc { + +// TODO: Add tests with SQLDescribeCol to check metadata of SQLColumns for ODBC 2 and +// ODBC 3. + +// Helper Functions + +std::wstring GetStringColumnW(SQLHSTMT stmt, int colId) { + SQLWCHAR buf[1024]; + SQLLEN lenIndicator = 0; + + SQLRETURN ret = SQLGetData(stmt, colId, SQL_C_WCHAR, buf, sizeof(buf), &lenIndicator); + + EXPECT_EQ(ret, SQL_SUCCESS); + + if (lenIndicator == SQL_NULL_DATA) { + return L""; + } + + // indicator is in bytes, so convert to character count + size_t charCount = static_cast(lenIndicator) / ODBC::GetSqlWCharSize(); + return std::wstring(buf, buf + charCount); +} + +// Test Cases + +TYPED_TEST(FlightSQLODBCTestBase, SQLTablesTestInputData) { + this->connect(); + + SQLWCHAR catalogName[] = L""; + SQLWCHAR schemaName[] = L""; + SQLWCHAR tableName[] = L""; + SQLWCHAR tableType[] = L""; + + // All values populated + SQLRETURN ret = SQLTables(this->stmt, catalogName, sizeof(catalogName), schemaName, + sizeof(schemaName), tableName, sizeof(tableName), tableType, + sizeof(tableType)); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + // Sizes are nulls + ret = SQLTables(this->stmt, catalogName, 0, schemaName, 0, tableName, 0, tableType, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + // Values are nulls + ret = SQLTables(this->stmt, 0, sizeof(catalogName), 0, sizeof(schemaName), 0, + sizeof(tableName), 0, sizeof(tableType)); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + // Close statement cursor to avoid leaving in an invalid state + SQLFreeStmt(this->stmt, SQL_CLOSE); + + // All values and sizes are nulls + ret = SQLTables(this->stmt, 0, 0, 0, 0, 0, 0, 0, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, SQLTablesTestGetMetadataForAllCatalogs) { + this->connect(); + + SQLWCHAR empty[] = L""; + SQLWCHAR SQL_ALL_CATALOGS_W[] = L"%"; + std::wstring expectedCatalogName = std::wstring(L"main"); + + // Get Catalog metadata + SQLRETURN ret = SQLTables(this->stmt, SQL_ALL_CATALOGS_W, SQL_NTS, empty, SQL_NTS, + empty, SQL_NTS, empty, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckStringColumnW(this->stmt, 1, expectedCatalogName); + CheckNullColumnW(this->stmt, 2); + CheckNullColumnW(this->stmt, 3); + CheckNullColumnW(this->stmt, 4); + CheckNullColumnW(this->stmt, 5); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, SQLTablesTestGetMetadataForNamedCatalog) { + this->connect(); + this->CreateTestTables(); + + SQLWCHAR catalogName[] = L"main"; + SQLWCHAR* tableNames[] = {(SQLWCHAR*)L"TestTable", (SQLWCHAR*)L"foreignTable", + (SQLWCHAR*)L"intTable", (SQLWCHAR*)L"sqlite_sequence"}; + std::wstring expectedCatalogName = std::wstring(catalogName); + std::wstring expectedTableType = std::wstring(L"table"); + + // Get named Catalog metadata - Mock server returns the system table sqlite_sequence as + // type "table" + SQLRETURN ret = SQLTables(this->stmt, catalogName, SQL_NTS, nullptr, SQL_NTS, nullptr, + SQL_NTS, nullptr, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + for (size_t i = 0; i < sizeof(tableNames) / sizeof(*tableNames); ++i) { + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckStringColumnW(this->stmt, 1, expectedCatalogName); + // Mock server does not support table schema + CheckNullColumnW(this->stmt, 2); + CheckStringColumnW(this->stmt, 3, tableNames[i]); + CheckStringColumnW(this->stmt, 4, expectedTableType); + CheckNullColumnW(this->stmt, 5); + } + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, SQLTablesTestGetSchemaHasNoData) { + this->connect(); + + SQLWCHAR SQL_ALL_SCHEMAS_W[] = L"%"; + + // Validate that no schema data is available for Mock server + SQLRETURN ret = SQLTables(this->stmt, nullptr, SQL_NTS, SQL_ALL_SCHEMAS_W, SQL_NTS, + nullptr, SQL_NTS, nullptr, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, SQLTablesTestGetMetadataForAllSchemas) { + this->connect(); + + SQLWCHAR empty[] = L""; + SQLWCHAR SQL_ALL_SCHEMAS_W[] = L"%"; + std::set actualSchemas; + std::set expectedSchemas = {L"$scratch", L"INFORMATION_SCHEMA", L"sys", + L"sys.cache"}; + + // Return is unordered and contains user specific schemas, so collect schema names for + // comparison with a known list + SQLRETURN ret = SQLTables(this->stmt, empty, SQL_NTS, SQL_ALL_SCHEMAS_W, SQL_NTS, empty, + SQL_NTS, empty, SQL_NTS); + + ASSERT_EQ(ret, SQL_SUCCESS); + + while (true) { + ret = SQLFetch(this->stmt); + if (ret == SQL_NO_DATA) break; + ASSERT_EQ(ret, SQL_SUCCESS); + + CheckNullColumnW(this->stmt, 1); + std::wstring schema = GetStringColumnW(this->stmt, 2); + CheckNullColumnW(this->stmt, 3); + CheckNullColumnW(this->stmt, 4); + CheckNullColumnW(this->stmt, 5); + + // Skip user-specific schemas like "@UserName" + if (!schema.empty() && schema[0] != L'@') { + actualSchemas.insert(schema); + } + } + + EXPECT_EQ(actualSchemas, expectedSchemas); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, SQLTablesTestFilterByAllSchema) { + // Requires creation of user table named ODBCTest using schema $scratch in remote server + this->connect(); + + SQLWCHAR SQL_ALL_SCHEMAS_W[] = L"%"; + SQLWCHAR* schemaNames[] = {(SQLWCHAR*)L"INFORMATION_SCHEMA", + (SQLWCHAR*)L"INFORMATION_SCHEMA", + (SQLWCHAR*)L"INFORMATION_SCHEMA", + (SQLWCHAR*)L"INFORMATION_SCHEMA", + (SQLWCHAR*)L"INFORMATION_SCHEMA", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys", + (SQLWCHAR*)L"sys.cache", + (SQLWCHAR*)L"sys.cache", + (SQLWCHAR*)L"sys.cache", + (SQLWCHAR*)L"sys.cache", + (SQLWCHAR*)L"$scratch"}; + std::wstring expectedSystemTableType = std::wstring(L"SYSTEM_TABLE"); + std::wstring expectedUserTableType = std::wstring(L"TABLE"); + + SQLRETURN ret = SQLTables(this->stmt, nullptr, SQL_NTS, SQL_ALL_SCHEMAS_W, SQL_NTS, + nullptr, SQL_NTS, nullptr, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + for (size_t i = 0; i < sizeof(schemaNames) / sizeof(*schemaNames); ++i) { + ValidateFetch(this->stmt, SQL_SUCCESS); + + const std::wstring& expectedTableType = + (std::wstring(schemaNames[i]).rfind(L"sys", 0) == 0 || + std::wstring(schemaNames[i]) == L"INFORMATION_SCHEMA") + ? expectedSystemTableType + : expectedUserTableType; + + CheckNullColumnW(this->stmt, 1); + CheckStringColumnW(this->stmt, 2, schemaNames[i]); + // Ignore table name + CheckStringColumnW(this->stmt, 4, expectedTableType); + CheckNullColumnW(this->stmt, 5); + } + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, SQLTablesGetMetadataForNamedSchema) { + // Requires creation of user table named ODBCTest using schema $scratch in remote server + this->connect(); + + SQLWCHAR schemaName[] = L"$scratch"; + std::wstring expectedSchemaName = std::wstring(schemaName); + std::wstring expectedTableName = std::wstring(L"ODBCTest"); + std::wstring expectedTableType = std::wstring(L"TABLE"); + + SQLRETURN ret = SQLTables(this->stmt, nullptr, SQL_NTS, schemaName, SQL_NTS, nullptr, + SQL_NTS, nullptr, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckNullColumnW(this->stmt, 1); + CheckStringColumnW(this->stmt, 2, expectedSchemaName); + // Ignore table name + CheckStringColumnW(this->stmt, 4, expectedTableType); + CheckNullColumnW(this->stmt, 5); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, SQLTablesTestGetMetadataForAllTables) { + this->connect(); + this->CreateTestTables(); + + SQLWCHAR SQL_ALL_TABLES_W[] = L"%"; + SQLWCHAR* tableNames[] = {(SQLWCHAR*)L"TestTable", (SQLWCHAR*)L"foreignTable", + (SQLWCHAR*)L"intTable", (SQLWCHAR*)L"sqlite_sequence"}; + std::wstring expectedCatalogName = std::wstring(L"main"); + std::wstring expectedTableType = std::wstring(L"table"); + + // Get all Table metadata - Mock server returns the system table sqlite_sequence as type + // "table" + SQLRETURN ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, + SQL_ALL_TABLES_W, SQL_NTS, nullptr, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + for (size_t i = 0; i < sizeof(tableNames) / sizeof(*tableNames); ++i) { + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckStringColumnW(this->stmt, 1, expectedCatalogName); + // Mock server does not support table schema + CheckNullColumnW(this->stmt, 2); + CheckStringColumnW(this->stmt, 3, tableNames[i]); + CheckStringColumnW(this->stmt, 4, expectedTableType); + CheckNullColumnW(this->stmt, 5); + } + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, SQLTablesTestGetMetadataForTableName) { + this->connect(); + this->CreateTestTables(); + + SQLWCHAR* tableNames[] = {(SQLWCHAR*)L"TestTable", (SQLWCHAR*)L"foreignTable", + (SQLWCHAR*)L"intTable", (SQLWCHAR*)L"sqlite_sequence"}; + std::wstring expectedCatalogName = std::wstring(L"main"); + std::wstring expectedTableType = std::wstring(L"table"); + + for (size_t i = 0; i < sizeof(tableNames) / sizeof(*tableNames); ++i) { + // Get specific Table metadata + SQLRETURN ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, + tableNames[i], SQL_NTS, nullptr, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckStringColumnW(this->stmt, 1, expectedCatalogName); + // Mock server does not support table schema + CheckNullColumnW(this->stmt, 2); + CheckStringColumnW(this->stmt, 3, tableNames[i]); + CheckStringColumnW(this->stmt, 4, expectedTableType); + CheckNullColumnW(this->stmt, 5); + + ValidateFetch(this->stmt, SQL_NO_DATA); + } + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, SQLTablesTestGetMetadataForUnicodeTableByTableName) { + this->connect(); + this->CreateUnicodeTable(); + + SQLWCHAR unicodeTableName[] = L"数据"; + std::wstring expectedCatalogName = std::wstring(L"main"); + std::wstring expectedTableName = std::wstring(unicodeTableName); + std::wstring expectedTableType = std::wstring(L"table"); + + // Get specific Table metadata + SQLRETURN ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, + unicodeTableName, SQL_NTS, nullptr, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckStringColumnW(this->stmt, 1, expectedCatalogName); + // Mock server does not support table schema + CheckNullColumnW(this->stmt, 2); + CheckStringColumnW(this->stmt, 3, expectedTableName); + CheckStringColumnW(this->stmt, 4, expectedTableType); + CheckNullColumnW(this->stmt, 5); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, SQLTablesTestGetMetadataForInvalidTableNameNoData) { + this->connect(); + this->CreateTestTables(); + + SQLWCHAR invalidTableName[] = L"NonExistantTableName"; + + // Try to get metadata for a non-existant table name + SQLRETURN ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, + invalidTableName, SQL_NTS, nullptr, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, SQLTablesGetMetadataForTableType) { + // Mock server only supports table type "table" in lowercase + this->connect(); + this->CreateTestTables(); + + SQLWCHAR tableTypeTableLowercase[] = L"table"; + SQLWCHAR tableTypeTableUppercase[] = L"TABLE"; + SQLWCHAR tableTypeView[] = L"VIEW"; + SQLWCHAR tableTypeTableView[] = L"TABLE,VIEW"; + SQLWCHAR* tableNames[] = {(SQLWCHAR*)L"TestTable", (SQLWCHAR*)L"foreignTable", + (SQLWCHAR*)L"intTable", (SQLWCHAR*)L"sqlite_sequence"}; + std::wstring expectedCatalogName = std::wstring(L"main"); + std::wstring expectedTableName = std::wstring(L"TestTable"); + std::wstring expectedTableType = std::wstring(tableTypeTableLowercase); + SQLRETURN ret = SQL_SUCCESS; + + ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, nullptr, SQL_NTS, + tableTypeTableUppercase, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, nullptr, SQL_NTS, + tableTypeView, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, nullptr, SQL_NTS, + tableTypeTableView, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + // Returns user table as well as system tables, even though only type table requested + ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, nullptr, SQL_NTS, + tableTypeTableLowercase, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + for (size_t i = 0; i < sizeof(tableNames) / sizeof(*tableNames); ++i) { + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckStringColumnW(this->stmt, 1, expectedCatalogName); + // Mock server does not support table schema + CheckNullColumnW(this->stmt, 2); + CheckStringColumnW(this->stmt, 3, tableNames[i]); + CheckStringColumnW(this->stmt, 4, expectedTableType); + CheckNullColumnW(this->stmt, 5); + } + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, SQLTablesGetMetadataForTableTypeTable) { + // Requires creation of user table named ODBCTest using schema $scratch in remote server + this->connect(); + + SQLWCHAR* typeList[] = {(SQLWCHAR*)L"TABLE", (SQLWCHAR*)L"TABLE,VIEW"}; + std::wstring expectedSchemaName = std::wstring(L"$scratch"); + std::wstring expectedTableName = std::wstring(L"ODBCTest"); + std::wstring expectedTableType = std::wstring(L"TABLE"); + SQLRETURN ret = SQL_SUCCESS; + + for (size_t i = 0; i < sizeof(typeList) / sizeof(*typeList); ++i) { + ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, nullptr, SQL_NTS, + typeList[i], SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckNullColumnW(this->stmt, 1); + CheckStringColumnW(this->stmt, 2, expectedSchemaName); + CheckStringColumnW(this->stmt, 3, expectedTableName); + CheckStringColumnW(this->stmt, 4, expectedTableType); + CheckNullColumnW(this->stmt, 5); + + ValidateFetch(this->stmt, SQL_NO_DATA); + } + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, SQLTablesGetMetadataForTableTypeViewHasNoData) { + this->connect(); + + SQLWCHAR empty[] = L""; + SQLWCHAR typeView[] = L"VIEW"; + + SQLRETURN ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, empty, + SQL_NTS, typeView, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + ret = SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, nullptr, SQL_NTS, + typeView, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCMockTestBase, SQLTablesGetSupportedTableTypes) { + this->connect(); + + SQLWCHAR empty[] = L""; + SQLWCHAR SQL_ALL_TABLE_TYPES_W[] = L"%"; + std::wstring expectedTableType = std::wstring(L"table"); + + // Mock server returns lower case for supported type of "table" + SQLRETURN ret = SQLTables(this->stmt, empty, SQL_NTS, empty, SQL_NTS, empty, SQL_NTS, + SQL_ALL_TABLE_TYPES_W, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckNullColumnW(this->stmt, 1); + CheckNullColumnW(this->stmt, 2); + CheckNullColumnW(this->stmt, 3); + CheckStringColumnW(this->stmt, 4, expectedTableType); + CheckNullColumnW(this->stmt, 5); + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +TEST_F(FlightSQLODBCRemoteTestBase, SQLTablesGetSupportedTableTypes) { + this->connect(); + + SQLWCHAR empty[] = L""; + SQLWCHAR SQL_ALL_TABLE_TYPES_W[] = L"%"; + SQLWCHAR* typeLists[] = {(SQLWCHAR*)L"TABLE", (SQLWCHAR*)L"SYSTEM_TABLE", + (SQLWCHAR*)L"VIEW"}; + + SQLRETURN ret = SQLTables(this->stmt, empty, SQL_NTS, empty, SQL_NTS, empty, SQL_NTS, + SQL_ALL_TABLE_TYPES_W, SQL_NTS); + + EXPECT_EQ(ret, SQL_SUCCESS); + + for (size_t i = 0; i < sizeof(typeLists) / sizeof(*typeLists); ++i) { + ValidateFetch(this->stmt, SQL_SUCCESS); + + CheckNullColumnW(this->stmt, 1); + CheckNullColumnW(this->stmt, 2); + CheckNullColumnW(this->stmt, 3); + CheckStringColumnW(this->stmt, 4, typeLists[i]); + CheckNullColumnW(this->stmt, 5); + } + + ValidateFetch(this->stmt, SQL_NO_DATA); + + this->disconnect(); +} + +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/visibility.h b/cpp/src/arrow/flight/sql/odbc/visibility.h new file mode 100644 index 000000000000..416dfecc8649 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/visibility.h @@ -0,0 +1,48 @@ +// 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. + +#pragma once + +#if defined(_WIN32) || defined(__CYGWIN__) +# if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4251) +# else +# pragma GCC diagnostic ignored "-Wattributes" +# endif + +# ifdef ARROW_FLIGHT_SQL_ODBC_STATIC +# define ARROW_FLIGHT_SQL_ODBC_EXPORT +# elif defined(ARROW_FLIGHT_SQL_ODBC_EXPORTING) +# define ARROW_FLIGHT_SQL_ODBC_EXPORT __declspec(dllexport) +# else +# define ARROW_FLIGHT_SQL_ODBC_EXPORT __declspec(dllimport) +# endif + +# define ARROW_FLIGHT_SQL_ODBC_NO_EXPORT +#else // Not Windows +# ifndef ARROW_FLIGHT_SQL_ODBC_EXPORT +# define ARROW_FLIGHT_SQL_ODBC_EXPORT __attribute__((visibility("default"))) +# endif +# ifndef ARROW_FLIGHT_SQL_ODBC_NO_EXPORT +# define ARROW_FLIGHT_SQL_ODBC_NO_EXPORT __attribute__((visibility("hidden"))) +# endif +#endif // Non-Windows + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif