From 912a3fee7536119778fa3d7320d9cb5642f3dd94 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 4 Dec 2025 11:34:53 -0800 Subject: [PATCH 1/2] Add Arrow Flight SQL ODBC driver Co-authored-by: alinalibq Co-authored-by: rscales Co-authored-by: justing-bq Co-authored-by: vic-tsang ODBC commits squashed into 1 to make rebasing to main easier. Add initial framework for odbc dll - Add ARROW_FLIGHT_SQL_ODBC option. If we set `ARROW_FLIGHT_SQL_ODBC=ON`, the flightsql odbc folder will be built - Add odbc api layer for entry_point.cc - builds odbc dll file, with ODBC APIs exported in odbc.def Address James' comments Fix `odbcabstraction` build errors and partially fix `flightsql_odbc` errors Fix boost-variant not found error - Adding dependencies from odbc/vcpkg.json to cpp/vcpkg.json - Fix whereami.cc and .h dependency; ported lates code Update whereami.cc - use `long` instead of `int64`. Fixed namespace issues. - PR CI fix: Add `parquet-testing` back Partial build fix for `flight_sql` folder - Replaced `namespace arrow` and `namespace odbcabstraction` with `using namespace ...` - fix flight_sql_connection.cc Fix `util::nullopt` to use `std::nullopt` - fix std::optional - fix BufferReader - Fix GetSchema - fix json_converter.cc - partial fix configuration.h - partial fix get_info_cache.cc - Fix winsock build error - Comment out `flight_sql` files that cannot build - Comment out configuration and unit tests - Comment out get info cache and system trust store Create initial odbc tests folder Implement SQLAllocEnv Fix cmake build Implement SQLFreeEnv Fix rest of build errors from `flightsql-odbc` - Fix get info errors - Fix for configuration window - added odbcinst library - Fix system trust store - unit test fixes - Add dependency of ARROW_COMPUTE. `arrow/compute/api.h` is used in `flight_sql`. Adding `ARROW_COMPUTE=ON` during build fixed run time unit tests failures. Implement SQLAllocConnect and SQLFreeConnect Fix build issue from static flight sql driver Lint and code style fixes Re-add deleted submodule parquet-testing clang-format lint fix cpplint lint fix Exclude whereami in rat exclude list C++/CLI lint fix Update parquet-testing to match commit from `main` Address Kou's comments ODBC directory lint fixes Catching the lint fixes outside of `flightsql-odbc` code Fix build warnings that get treated as error Implement SQLSetEnvAttr and SQLGetEnvAttr Implement use of ExecuteWithDiagnostics Doxygen Error Fixes and Address comments from Kou and James Address comments from Kou - Updates License.txt - Update cmake toolchain - Move whereami to `vendored` - Use string_view instead of NOLINT std::string Remove `whereami.cc` from arrow util build We are building whereami.cc as part of odbc Fix include headers to replace <> with "" Address comments from James Implement SQLGetDiagField SQLDriverConnect, SQLConnect and SQLDisconnect Implement stubs for SQLGetInfo, SQLGetDiagField and SQLGetDiagRec Separate RegisterDsn and UnregisterDsn from windows build Update code to save driver value from connection string Add ReadMes for ODBC and tests Fix test issues with string_view Address code reviews Update entry_points.cc to fix build issue Remove Dremio references Use emplace properly Address comment from Rob and add SQLDisconnect test case Add odbc.def and cmd file to rat_exclude Nit - remove duplicate lines Accidentally committed the change during git rebase Nit - remove usage of nullptr DSN window implementation Add licenses to `.cmd` and `.def` files Implement SQLGetDiagRec Build ODBC in Windows Workflow Tests are skipped for now. Updates for SQLGetDiagFieldW Enable Driver Logging Add todo to update logging system later Add logs Implement ODBC API with debug logging Enable mock test * Add todo for noauth validation * mock server with token auth Add tests * run same test with both modes * Enable ODBC tests in workflow * Switch current test cases to use FlightSQLODBCTestBase So the tests can be skipped when `TEST_CONNECT_STR` is not set. * Change tests to run on both mock and remote modes Wrap usage of TEST_CONNECT_STR where possible * Rename test fixtures and make connection string functions virtual * Fix lint issue * Attempt to enable ODBC build on Windows platforms * Attempt to fix clang64 and MinGW errors * Attempt to register ODBC * Address James' comments Use constant string for token * use ServerMiddleware to validate token Fix connection issues to DBT Labs PopulateCallOptions before making a connection Fix dsn window bug with advance properties Fix seg fault issue from empty string Implement SQLAllocStmt Follow-up DBT Labs connection fix Implement SQLGetDiag Rec and Field for statement Unicode support for DSN ODBC APIs * Let compiler append `W` to ODBC APIs where applicable. Initialize Kernel functions As per changes from #46261, we need to initialize Kernel library explicitly to get the functions registered Implement SQLSetConnectAttr and SQLGetConnectAttr ODBC Test Segfault Fix * Move `connection_test.cc` to last in `CMakeLists.txt`, this resolves the seg fault. The seg fault was not reproducible on my local environment, even when I use the msys2 shell. * Rename test executable to flight_sql_odbc_test SQLExecDirect implementation * SQLGetStmtAttr stub implementation * Fix missing break statement in SQLGetDiagRec * Run ShutdownProtobufLibrary as part of test environment * Add comment for GH-46889 * Pass call options to executed prepared statement `call_options_` contains the authentication token which is needed Implement SQLGetInfo SQLGetInfo Workflow Fixes SQLFetch & SQLGetData Implementation SQLGetStmtAttr stub implementation Stub call for SQLGetStmtAttr Enable statement handle allocation Implement SQLExecDirect - test hang issue Update odbc_api.cc Run `ShutdownProtobufLibrary` after all ODBC tests Resolves test hanging problem Fix missing break statement in SQLGetDiagRec Add tests Lint fixes Run ShutdownProtobufLibrary as part of test environment Add debug messages Draft code Remove drafts Add comment Add comment for GH-46889 Update statement test headers Pass call options to executed prepared statement `call_options_` contains the authentication token which is needed SQLFetch & SQLGetData initial impl EVERYTHING before this commit is for SQLExecDirect SQLFetch and SQLGetData TestSQLExecDirectSimpleQuery test Make GetData() return SQLRETURN `TestSQLExecDirectDataQuery` for remote and mock servers Remove unneeded test case Add more data types * Add SQL_GUID in getCTypeForSQLType * Add data types in query * Add checks for columns 1 to 25 * Update statement_test.cc Add varbinary test * Implement SQLNumResultCols Will leave tests for later * Implement SQLRowCount * Implement SQLMoreResults * Work on date retrieval fix The `TestSQLExecDirectDataQuery` checks for date is not working. * Fix date retrieval error by importing fix from Paul * imported fix from https://github.com/dremio/flightsql-odbc/commit/d44d862bd07f05328f5fcfd68a8f40357aa072fa, an Apache-Licensed repository * Add more checks for get data * Add more tests * Continue work on float truncation test * Disable float truncation test * Address comments from James - do not return errors for invalid rowCountPtr and columnCountPtr - add static cast for columnCount - Add tests with SQL_C_DEFAULT as target type - Add test checks for indicator - Fix SQL_NUMERIC, SQL_BIT, SQL_FLOAT target type * Fix build issues in CI * More fix for CI * Add test for invalid buffer length SQLPrepare & SQLExecute Implementation * Add tests * Address comments from Rob Code Style fixes * Move ifdefs outside of test cases * use `stmt` * Move logs to first line * fix test for `SQL_DRIVER_HSTMT` * Use camel case for tests Implement SQLGetStmtAttr and SQLSetStmtAttr SQLBindCol Implementation * SQL_UNBIND implementation + tests * Add tests for `SQL_ATTR_ROW_ARRAY_SIZE` * Add check for `SQL_ATTR_ROWS_FETCHED_PTR` Enable TestCloseConnectionWithOpenStatement test case SQLFetchScroll Implementation * Add checks for SQL_ATTR_ROWS_FETCHED_PTR * SQLFetchScroll is supposed to return the number of rows fetched using SQL_ATTR_ROWS_FETCHED_PTR SQLExtendedFetch Implementation * Implement rowCountPtr and rowStatusArray for SQLExtendedFetch * Add tests for SQLExtendedFetch * `SQLExtendedFetch` doesn't return `SQL_SUCCESS_WITH_INFO` for error state 22002. * Add tests for `SQL_ROWSET_SIZE` * Address comments from James GH-46584 Iterate endpoint * use suggestion from James for one-liner change to return `Status::OK` directly * fix for iterating endpoints, it is based on JDBC's fix for the same bug * save value of `FlightClientOptions` * Use `TestFlightServer` Add `arrow_flight_sql_shared` to enable usages for `TestFlightServer` * Fix build errors from missing `gmock` * Add checks for array data Update flight_sql_stream_chunk_buffer_test.cc Add `FlightStreamChunkBufferTest` unit test Draft test with `FlightSQLODBCMockEndpointTestBase` * Reference GH-47117 and Clean up code * Add driver and DSN to built-in properties to not pass driver/dsn as attributes to server * Allow `=` in values inside connection strings, fixes connectivity problems * Address comments from Rob SQLColumns Implementation * Initial impl for SQLColumns * Add test for SQLColumns * Add columns test with all supported column types - mock server doesn't support schema * Address comments from James - Test different data type return value for SQLColumns - Add todo comment for SQLDescribeCol tests --------- Co-authored-by: rscales Fix bug of DSN not read properly * Fix reading DSN bug Use a simpler, more robust way to load the DSN SQLColAttribute implementation * SQLColAttribute initial impl * Switch to use `GetAttributeSQLWCHAR` to be unicode-compatible * Add SQLColAttribute tests and fix bugs in `flightsql-odbc` impl * Fixed bug with incorrect column count. It was returning column count + 1. * Fixed bug with incorrect numPrecRadix. It was returning SQL_NO_TOTAL (maps to -4) for non-numeric columns, but the correct value is 0. * Fixed bug with unsigned column to return true for unsigned columns (non-numeric columns or unsigned numeric columns) and false for signed columns (numeric columns) * Fixed bug with incorrect non-concise data type return value for date time type types. The correct return is SQL_DATETIME for all date time types * Address comments from James * use const for `ConvertToWString` * add nullptr check in odbc_api.cc layer * Add support for `SQL_COLUMN_LENGTH`, `SQL_COLUMN_PRECISION`, and `SQL_COLUMN_SCALE`. The driver will return the same values for ODBC2 and ODBC3. * Add tests for ODBC v2 SQLColAttributes values * I converted tests that can be run on remote servers where possible * Address comment from Rob Implement SQLTables Use C++ 20 standard for ODBC * Add support for building with C++20 * Upgrade spdlogs version as an attempt to fix build issues * Fix issue of missing unique_ptr definition Add tests for SQLMoreResults Implement SQLNativeSql Add diagnostic records * remove `SQLErrors`, as the driver manager is supposed to map `SQLErrors` to `SQLGetDiagRec` Implement tests for SQLNumResultCols SQLCloseCursor Implementation * fix error state to return the correct state 24000. * add tests for SQLCloseCursor and SQLFreeStmt(SQL_CLOSE) Add SQLColumns and SQLColAttribute tests with nullptr * Modify SQLTables test to run on both mock and remote * reorder error msg alphabetically Implement tests for SQLRowCount Fix merge conflicts Adjust searchable return value based on GH-46920 fix Fix `getCTypeForSQLType` return for interval types - Fix `getCTypeForSQLType` function to return the correct `C type` for interval SQL data types. Previously the function was checking for interval `C type` and returning interval `SQL type`, which was the opposite of the intended functionality. SQLGetTypeInfo implementation * Add tests for SQLGetTypeInfo * fix bug of createParams returning empty string instead of null * fix bug of non-concise data type being returned for datetime/interval * fix bug of longvarchar not converted to wlongvarchar when driver has unicode enabled * Address comments from James and add checks * test SQL_ALL_TYPES in ODBC 2.x. * test SQL_TYPE_* in ODBC 2.x, the driver manager reports invalid data type error. * test SQL_* datetime in ODBC 3.x for backwards compatibility. * add check for valid/invalid data type. Fix segfault issue from empty metadata * Use empty map in bug fix * Address comments from James Implement SQLDescribeCol Add SQLError Tests * add test to free null handles. Without handle value initialization, segfault error was seen Move `SQLGetDiagField` and `SQLGetDiagRec` tests to `errors_test.cc` * Address comments from James * Add ODBC Ver 2 tests * update test name to indicate if error handling is from driver manager. * add tests for warnings. * fix lint errors. * remove SQL_ATTR_APP_ROW_DESC that is not applicable to Environment Attribute. Add tests for SQLGetFunctions * Using `SQL_FUNC_EXISTS` macro fixed the issue of `api_exists` not read correctly * Fix SQLGetTypeInfo naming for ODBC ver 2 tests * Add more tests Add unsupported API checks. Add `TestSQLGetFunctionsODBCVer2`. Add `TestSQLGetFunctionsCheckSingleAPI`. * Update reset value * Add SQLDescribeCol function check Add additional SQLDescribeCol test cases Add Descriptor support in SQLAllocHandle and SQLFreeHandle * Descriptor allocation initial impl * Add descriptor handle tests * Add diagnostic error test for descriptor handle * the error is from driver manager as our driver doesn't have descriptor-specific APIs implemented yet * Add SQLGetDescField test from driver manager * Add doxygen doc comment Fix connection issues servers that require data in handshake * revert back to passing empty string in handshake Fix bug of system trust store not loaded properly * Bug: system trust store has default value of "true" regardless of "encryption" value. * But if encryption is set to false, system trust store cannot be used, so system trust store should be saved as false if user does not enable encryption. * It may confuse the user if they see encryption is set to false but system trust store is set to true. In this case, the driver will not do system trust store verification. The DSN window should reflect this accurately. CPack ODBC Windows msi installer Add ARROW_FLIGHT_SQL_ODBC_INSTALLER environment variable to enable creation of Windows installer. * Use CPack + WiX to create a `msi` installer. * run command `cpack` under the build directory to generate the installer. Register ODBC Driver on Windows in installer * In `wxs`, cannot use `package`, need to use `fragment` instead * Use component as feature will automatically be generated * Set Patch version + add installer instructions Use Arrow logo banner * replaces default banner with Arrow logo banner Indicate Driver Company and Version on Driver Manager * use versioninfo.rc.in template * use `1 VERSIONINFO` for it to work properly * Set version variables * Add `.rc` to gitignore * Add `@` variables to rc template Return nameLengthPtr value as length in characters Allow spaces while parsing Table Type mac changes added changes to build in MacOS Enable ODBC build on Windows Glib & Ruby workflows * Fix `boost::get` issue on GLib workflow * Fix GLib build issue with conflicts Compile entry_points.cc before odbc_api.cc due to conflict from sql.h and flight/types.h. Fix build warnings treated as error on Windows SQLDescribeCol expects `bufCharLen` to be of type `SQLSMALLINT`, so build warnings occur when `bufCharLen` is set to `SQLINTEGER`. Add more types for Array Conversion Set column attribute SQL_DESC_TYPE_NAME [Refactor] Remove reference of `Configuration` in odbc_connection.cc Remove reference of `Configuration` in odbc_connection.cc which is in library `odbcabstraction`. The reasoning is that `Configuration` is in `arrow_odbc_spi_impl` and this is a library that `odbcabstraction` depends on. I moved the logic for reading DSN values into `odbc_api.cc` instead, since that is a place that will result in no conflicts. The ODBC driver now does not save the DSN value in the connection string if the DSN value is put after Driver value. * Address comments from James Simply logic to check for first key value pair only Add TODO for non-UTC time zone support * Currently, the driver only supports UTC time zone Replace `spdlogs` with Arrow's Internal Logging * Add logging README * register kernel function conditionally * Resolves errors of kernel function already registered Logging files are not supported since GLOG is not enabled on Windows in Arrow repo. We can work + test on log file support on macOS/Linux phase Remove `RUN_ALL_TESTS` as they are not needed Previously, tests on Windows were not being run due to https://github.com/apache/arrow/issues/47434. So we added `RUN_ALL_TESTS` as a workaround in early May, before https://github.com/apache/arrow/issues/47434 was raised. Now that https://github.com/apache/arrow/issues/47434 is resolved, we can remove `RUN_ALL_TESTS` as they are not needed. Other changes: * Renamed `arrow_odbc_spi_impl_test` to `odbc_spi_impl_test` so the test executable will be `arrow_odbc_spi_impl_test.exe` instead of `arrow_arrow_odbc_spi_impl_test.exe` Fix Function & Variable Naming Fix member variable naming Address code review comments for Arrow#40939 * Reword comment on null check Addresses comment https://github.com/apache/arrow/pull/40939#discussion_r2099120394 * Use `std::memcpy` instead of `memcpy` Addresses comment https://github.com/apache/arrow/pull/40939#discussion_r2099120764 * add include cstring * Remove warpdrive mentions * Fix lint * Address Justin's comments Combine utils and define utils namespace Use arrow::flight::sql::odbc namespace Update namespaces driver::flight_sql and driver::odbcabstraction to arrow::flight::sql::odbc. Fix seg fault from register log PR to keep apache-odbc in sync with arrow main branch. I found that, if the driver registers logs before compute kernel registration, segfault somehow occurs at when register kernel function is being used. I am not sure the root cause, but registering the logs after compute kernel registration seems to resolve the issue. Refactor odbcabstraction and odbc_impl flight_sql is renamed to odbc_impl. The files inside odbcabstraction have been moved to odbc_impl and odbcabstraction is removed. Replace boost::variant with std::variant * Remove `boost-optional` and `boost-variant` dependencies from vcpkg.json * Replace boost::variant with std::variant Fix EXPECT_EQ() to have expected on the left and actual on the right Code style naming convention Remove underscores in test names Sync arrow_flight_testing changes from Arrow::main branch Fix test failure due to typo Fix `FlightSQLODBCRemoteTestBase.TestSQLExecDirectDataQueryDefaultType` test failure due to typo The precision should be 38 in the original code. I think it got changed to 0 by accident somewhere. Disable tests with `DISABLED_` * Enable `TestSQLGetInfoDriverHdesc` We missed this test before, enabling the test now * Disable tests with `DISABLED_` Fix test assertions Define Test Fixtures * Connect/disconnect in SetUp/TearDown * Define Test Fixtures Resolve ODBC CI Build Errors * Fix `ConfigDSNW` not found error * Fix `TestFreeNullHandles` test `ConnectionTest` sets up the handles, so the `this->...` handles will not be null at beginning of test. Define New Base Test Fixtures Skip tests without connecting/disconnecting Address general test code review comments * Use RAII helper for allocating and freeing env/conn handles Avoids duplicated code Move test helper functions to anonymous namespace Add `[[nodiscard]]` for ODBC APIs Addresses community comment: https://github.com/apache/arrow/pull/47763/files#r2450014186 Remove `using List=` in test suite definitions Use static_cast for `SQLWCHAR` in type info test Use static_cast for `SQLWCHAR` in tables test * use mutable arrays for places where characters cannot be const Use static_cast for `SQLWCHAR` in columns test Update comment Use static_cast for `SQLWCHAR` in SQLGetInfo test * Address Justin's comments * Add fix for skipping the allocation of handles ^ Conflicts: ^ .github/workflows/cpp.yml ^ cpp/src/arrow/flight/sql/odbc/odbc_api.cc ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/encoding_utils.h ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.cc ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection_test.cc ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/main.cc ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.cc ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.h ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/connection.h ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.h ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.cc ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc ^ cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h ^ cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc ^ cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc Enable ODBC build in MacOS CI ODBC Standalone MSVC Build CI Remove DataSet Fix cache key Use odbc-specific cache key Fix Lint and Add odbc registration step Link appropriate GitHub issues that blocks ODBC tests Disable test phase and add schedule Run everyday 7 am Vancouver time Enable ODBC build on MSVC CI Code Clean up and enable ODBC tests in CI Still need to modify ODBCUtilEnvironment if we decide to use it. Still need to add ODBC V2 support so a different env and conn is used for ODBC 2 tests. Draft enable ODBC global setup/teardown Run ODBC test once outside of test script If ODBC test is run using MinGW Shell, segfault occurs. I am not getting any seg fault on my local MSVC Windows when I run the tests without the bash script. But if Windows CI breaks from running the standalone exe then I will look into this Prepend vcpkg to search vcpkg before `/lib/cmake` Since we are installing dependencies on vcpkg, if `lib/cmake` is searched first, then cmake will look into that directory and use the wrong paths. Convert VCPKG Windows path to MSYS path Set `VCPKG_ROOT` in test phase Fix ODBC dll name Use `arrow_flight_sql_odbc.dll` instead of `libarrow_flight_sql_odbc.dll` which is the naming convention used on MinGW Windows Link ODBC library on all platforms On my local MSVC Windows machine, Visual Studio is used to build without needing to link ` ${ODBCINST}` explicitly, its behavior might be different from Ninja which is what CI uses. `${ODBCINST}` is likely needed by Linux as well, so adding it for all platforms. Attempt to resolve `arrow-compute-grouper-benchmark` build issue I think `if(ARROW_FLIGHT_TEST_LINKAGE STREQUAL "static")` might be more appropriate here, as I am seeing build issues due to both dynamic and static linking occurring. I see in https://github.com/apache/arrow/commit/59903d089ec5b1766e1622d94c1f618b539b205d, this check is used for `arrow-filesystem-s3fs-benchmark` Disable `UNITY_BUILD` for ODBC test due to conflict on `sqlite_sql_info.cc` On some workflows, unity build is set to ON to make the build faster. This is an attempt to resolve the build issue. Remove debug messages Fix lint Add `sql_info_undef.h` to sqlite_sql_info.cc Undefine duplicates in SQLGetInfo Add `#pragma once` to odbc_test_suite.h To avoid redefinition error Attempt to resolve conflict with sql/types.h Attempt to include Arrow headers before ODBC headers to avoid conflict with arrow/flight/sql/types.h [GH-48084] Replace boost::optional with std::optional Addresses comment https://github.com/apache/arrow/pull/40939#discussion_r2099118497 Replace boost::optional with std::optional in ODBC codebase https://github.com/apache/arrow/issues/48084 Fix lint Remove `BOOST_SOURCE=BUNDLED` to use vcpkg's dynamic link to boost Since we need to use link to boost dynamically, we need to remove the bundled boost library flag. Add debug messages. Remove `ARROW_BOOST_USE_SHARED = OFF` Since we have a release build, can enable boost as shared. With `ARROW_BOOST_USE_SHARED = OFF`, I am getting error ``` D:\a\arrow\arrow\build\cpp\vcpkg_installed\x64-windows\include\boost/filesystem/config.hpp(96): fatal error C1189: #error: Must not define both BOOST_FILESYSTEM_DYN_LINK and BOOST_FILESYSTEM_STATIC_LINK ``` Also increased time limit, since 2 hours don't seem to be enough to finish the vcpkg build Change to release build Getting ``` orc.lib(Exceptions.cc.obj) : error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': value '0' doesn't match value '2' in unity_2_cxx.cxx.obj orc.lib(Exceptions.cc.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MD_DynamicRelease' doesn't match value 'MDd_DynamicDebug' in unity_2_cxx.cxx.obj ``` when building with debug and presumably ARROW_ORC Set ARROW_BOOST_USE_SHARED to OFF `ARROW_BOOST_USE_SHARED` is restored to `OFF`, which according to Windows doc should help with the debug build now. Retore value of `ARROW_BUILD_BENCHMARKS`, though noting it is set to `OFF` in GLib MSVC workflow. Add VCPKG set up Borrowed from the Glib workflow Add `/EHsc` flag * Add back `CMAKE_CXX_STANDARD: "17"` Remove `CMAKE_CXX_STANDARD` 17 * reason: gtest can't be used with C++17. Arrow project doesn't support C++ 17 yet. Extend run-time to 2hr windows-mingw also has timeout of 2hr Empty commit to trigger CI Specify `VCPKG_BINARY_SOURCES` and `VCPKG_DEFAULT_TRIPLET` Specify VCPKG_TARGET_TRIPLET as x64 windows Set ARROW_DEPENDENCY_SOURCE to VCPKG To make it easier to manage dependencies Enable static build on MSVC `ARROW_BUILD_BENCHMARKS` prevents Flight from being built Remove `ARROW_BOOST_USE_SHARED` Having static vs. shared issue Enable Flight & Flight SQL for ODBC Enable ODBC build on MSVC CI Enable regular ctest tests Remove undef items Add concurrency and permissions to odbc yml Create cpp_odbc.yml Fix architecture in CI * Add ODBC installer MSVC CI Mac Setup ODBC ini Script * added setup script for mac and platform folders updated read me windows file path updated windows folder related file paths added license Fix Lint issues Add install ODBC script Update install_odbc_ini.sh This script depends on the ODBC install script. Add TODO note and update DSN * Fix typo in README --------- Fix lint Fix lint Update flight_sql_stream_chunk_buffer.cc Update statement_test.cc Move MacOS ODBC workflows to cpp_odbc.yml Fix Lint Fix `ODBC::GetSqlWCharSize()` Add back missing checks for error handle tests SQLError implementation in macOS * SQLError implementation for macOS Disable `SQLGetConnectOption` on macOS. Still need to check if SQLGetConnectOption is needed on macOS Excel. Co-Authored-By: Victor Tsang Co-Authored-By: Alina (Xi) Li Co-Authored-By: vic-tsang --- .github/workflows/cpp.yml | 7 +- .github/workflows/cpp_odbc.yml | 242 +++ .gitignore | 4 + ci/scripts/cpp_build.sh | 4 +- ci/scripts/cpp_test.sh | 1 + cpp/Brewfile | 1 + cpp/CMakeLists.txt | 8 +- cpp/CMakePresets.json | 23 + cpp/cmake_modules/BuildUtils.cmake | 17 +- cpp/cmake_modules/DefineOptions.cmake | 4 +- cpp/cmake_modules/SetupCxxFlags.cmake | 1 + cpp/cmake_modules/ThirdpartyToolchain.cmake | 13 +- cpp/src/arrow/CMakeLists.txt | 13 +- cpp/src/arrow/flight/sql/column_metadata.cc | 11 +- cpp/src/arrow/flight/sql/odbc/CMakeLists.txt | 97 +- cpp/src/arrow/flight/sql/odbc/README | 83 + cpp/src/arrow/flight/sql/odbc/entry_points.cc | 12 + .../sql/odbc/install/mac/install_odbc.sh | 76 + .../sql/odbc/install/mac/install_odbc_ini.sh | 82 + .../windows/arrow-flight-sql-odbc-patch.xml | 22 + .../install/windows/arrow-flight-sql-odbc.wxs | 37 + .../odbc/install/windows/arrow-wix-banner.bmp | Bin 0 -> 29930 bytes .../odbc/install/windows/versioninfo.rc.in | 54 + cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 330 ++- .../arrow/flight/sql/odbc/odbc_api_internal.h | 6 + .../flight/sql/odbc/odbc_impl/CMakeLists.txt | 2 +- .../accessors/binary_array_accessor.cc | 3 +- .../accessors/binary_array_accessor_test.cc | 7 +- .../accessors/boolean_array_accessor_test.cc | 5 +- .../sql/odbc/odbc_impl/accessors/common.h | 5 +- .../accessors/date_array_accessor_test.cc | 7 +- .../accessors/decimal_array_accessor_test.cc | 5 +- .../primitive_array_accessor_test.cc | 23 +- .../accessors/string_array_accessor.cc | 3 +- .../accessors/string_array_accessor_test.cc | 11 +- .../accessors/time_array_accessor_test.cc | 11 +- .../accessors/timestamp_array_accessor.cc | 10 +- .../timestamp_array_accessor_test.cc | 11 +- .../sql/odbc/odbc_impl/accessors/types.h | 2 +- .../flight/sql/odbc/odbc_impl/address_info.cc | 8 +- .../flight/sql/odbc/odbc_impl/address_info.h | 5 +- .../sql/odbc/odbc_impl/attribute_utils.h | 22 +- .../sql/odbc/odbc_impl/calendar_utils.cc | 3 + .../odbc/odbc_impl/config/configuration.cc | 6 +- .../sql/odbc/odbc_impl/config/configuration.h | 6 +- .../sql/odbc/odbc_impl/encoding_utils.h | 26 +- .../odbc/odbc_impl/flight_sql_auth_method.cc | 28 +- .../odbc/odbc_impl/flight_sql_connection.cc | 13 +- .../odbc/odbc_impl/flight_sql_connection.h | 2 +- .../odbc_impl/flight_sql_connection_test.cc | 18 +- .../odbc_impl/flight_sql_get_tables_reader.cc | 6 +- .../odbc_impl/flight_sql_result_set_column.h | 1 + .../flight_sql_result_set_metadata.cc | 48 +- .../flight_sql_result_set_metadata.h | 3 +- .../odbc/odbc_impl/flight_sql_ssl_config.h | 4 +- .../odbc/odbc_impl/flight_sql_statement.cc | 4 +- .../flight_sql_statement_get_columns.cc | 13 +- .../flight_sql_statement_get_tables.h | 1 - .../flight_sql_statement_get_type_info.cc | 8 +- .../flight_sql_stream_chunk_buffer.cc | 14 +- .../sql/odbc/odbc_impl/get_info_cache.h | 5 +- .../sql/odbc/odbc_impl/json_converter.cc | 2 +- .../sql/odbc/odbc_impl/json_converter.h | 2 +- .../sql/odbc/odbc_impl/json_converter_test.cc | 5 +- .../sql/odbc/odbc_impl/odbc_connection.cc | 23 +- .../sql/odbc/odbc_impl/odbc_connection.h | 12 +- .../sql/odbc/odbc_impl/odbc_descriptor.cc | 76 +- .../sql/odbc/odbc_impl/odbc_descriptor.h | 6 +- .../flight/sql/odbc/odbc_impl/odbc_handle.h | 18 +- .../sql/odbc/odbc_impl/odbc_statement.cc | 31 +- .../sql/odbc/odbc_impl/odbc_statement.h | 9 +- .../odbc/odbc_impl/parse_table_types_test.cc | 3 +- .../odbc/odbc_impl/record_batch_transformer.h | 4 +- .../record_batch_transformer_test.cc | 3 +- .../odbc/odbc_impl/scalar_function_reporter.h | 2 +- .../sql/odbc/odbc_impl/spi/connection.h | 5 +- .../odbc/odbc_impl/spi/result_set_metadata.h | 6 +- .../flight/sql/odbc/odbc_impl/spi/statement.h | 6 +- .../sql/odbc/odbc_impl/ui/custom_window.cc | 1 + .../odbc_impl/ui/dsn_configuration_window.cc | 6 +- .../flight/sql/odbc/odbc_impl/ui/window.cc | 1 - .../arrow/flight/sql/odbc/odbc_impl/util.cc | 27 +- .../arrow/flight/sql/odbc/odbc_impl/util.h | 26 +- .../flight/sql/odbc/odbc_impl/util_test.cc | 26 +- .../flight/sql/odbc/tests/CMakeLists.txt | 9 +- cpp/src/arrow/flight/sql/odbc/tests/README | 23 + .../flight/sql/odbc/tests/columns_test.cc | 1763 ++++++++++++++- .../sql/odbc/tests/connection_attr_test.cc | 2 +- .../flight/sql/odbc/tests/connection_test.cc | 2 +- .../sql/odbc/tests/dremio/docker-compose.yml | 35 + .../tests/dremio/set_up_dremio_instance.sh | 66 + .../flight/sql/odbc/tests/errors_test.cc | 98 +- .../sql/odbc/tests/get_functions_test.cc | 220 ++ .../flight/sql/odbc/tests/odbc_test_suite.cc | 6 +- .../flight/sql/odbc/tests/odbc_test_suite.h | 6 + .../sql/odbc/tests/statement_attr_test.cc | 11 +- .../flight/sql/odbc/tests/statement_test.cc | 119 +- .../flight/sql/odbc/tests/tables_test.cc | 93 + .../flight/sql/odbc/tests/type_info_test.cc | 1897 +++++++++++++++++ cpp/src/arrow/vendored/whereami/whereami.cc | 2 +- cpp/vcpkg.json | 2 - 101 files changed, 5805 insertions(+), 346 deletions(-) create mode 100644 .github/workflows/cpp_odbc.yml mode change 100755 => 100644 ci/scripts/cpp_test.sh create mode 100644 cpp/src/arrow/flight/sql/odbc/README create mode 100644 cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc.sh create mode 100644 cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc_ini.sh create mode 100644 cpp/src/arrow/flight/sql/odbc/install/windows/arrow-flight-sql-odbc-patch.xml create mode 100644 cpp/src/arrow/flight/sql/odbc/install/windows/arrow-flight-sql-odbc.wxs create mode 100644 cpp/src/arrow/flight/sql/odbc/install/windows/arrow-wix-banner.bmp create mode 100644 cpp/src/arrow/flight/sql/odbc/install/windows/versioninfo.rc.in create mode 100644 cpp/src/arrow/flight/sql/odbc/tests/README create mode 100644 cpp/src/arrow/flight/sql/odbc/tests/dremio/docker-compose.yml create mode 100644 cpp/src/arrow/flight/sql/odbc/tests/dremio/set_up_dremio_instance.sh create mode 100644 cpp/src/arrow/flight/sql/odbc/tests/get_functions_test.cc create mode 100644 cpp/src/arrow/flight/sql/odbc/tests/type_info_test.cc diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml index 6586480a81e..cfb7ff171da 100644 --- a/.github/workflows/cpp.yml +++ b/.github/workflows/cpp.yml @@ -310,7 +310,8 @@ jobs: ARROW_DATASET: ON ARROW_FLIGHT: ON ARROW_FLIGHT_SQL: ON - ARROW_FLIGHT_SQL_ODBC: OFF + ARROW_FLIGHT_SQL_ODBC: ON + ARROW_FLIGHT_SQL_ODBC_INSTALLER: ON ARROW_GANDIVA: ON ARROW_GCS: ON ARROW_HDFS: OFF @@ -389,6 +390,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\tests\install_odbc.cmd" ${{ github.workspace }}\build\cpp\%ARROW_BUILD_TYPE%\libarrow_flight_sql_odbc.dll - name: Test shell: msys2 {0} run: | diff --git a/.github/workflows/cpp_odbc.yml b/.github/workflows/cpp_odbc.yml new file mode 100644 index 00000000000..e4cfacb7f88 --- /dev/null +++ b/.github/workflows/cpp_odbc.yml @@ -0,0 +1,242 @@ +# 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. + +name: C++ ODBC + +on: + push: + branches: + - '**' + - '!dependabot/**' + tags: + - '**' + paths: + - '.github/workflows/cpp_odbc.yml' + - 'ci/scripts/cpp_*' + - 'cpp/src/arrow/flight/sql/odbc/*' + pull_request: + paths: + - '.github/workflows/cpp_odbc.yml' + - 'ci/scripts/cpp_*' + - 'cpp/src/arrow/flight/sql/odbc/*' + schedule: + - cron: '0 13 * * *' + +concurrency: + group: ${{ github.repository }}-${{ github.head_ref || github.sha }}-${{ github.workflow }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + windows: + runs-on: windows-2022 + timeout-minutes: 240 + env: + ARROW_BUILD_SHARED: ON + ARROW_BUILD_STATIC: ON + ARROW_BUILD_TESTS: ON + ARROW_BUILD_TYPE: release + ARROW_DEPENDENCY_SOURCE: VCPKG + ARROW_FLIGHT: ON + ARROW_FLIGHT_SQL: ON + ARROW_FLIGHT_SQL_ODBC: ON + ARROW_FLIGHT_SQL_ODBC_INSTALLER: ON + ARROW_SIMD_LEVEL: AVX2 + CMAKE_CXX_STANDARD: "17" + CMAKE_GENERATOR: Ninja + CMAKE_INSTALL_PREFIX: /usr + VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' + VCPKG_DEFAULT_TRIPLET: x64-windows + steps: + - name: Disable Crash Dialogs + run: | + reg add ` + "HKCU\SOFTWARE\Microsoft\Windows\Windows Error Reporting" ` + /v DontShowUI ` + /t REG_DWORD ` + /d 1 ` + /f + - name: Checkout Arrow + uses: actions/checkout@v5 + with: + fetch-depth: 0 + submodules: recursive + - name: Download Timezone Database + shell: bash + run: ci/scripts/download_tz_database.sh + - name: Install msys2 (for tzdata for ORC tests) + uses: msys2/setup-msys2@v2 + id: setup-msys2 + - name: Install cmake + shell: bash + run: | + ci/scripts/install_cmake.sh 4.1.2 /usr + - name: Install ccache + shell: bash + run: | + ci/scripts/install_ccache.sh 4.12.1 /usr + - name: Setup ccache + shell: bash + run: | + ci/scripts/ccache_setup.sh + - name: ccache info + id: ccache-info + shell: bash + run: | + echo "cache-dir=$(ccache --get-config cache_dir)" >> $GITHUB_OUTPUT + - name: Cache ccache + uses: actions/cache@v4 + with: + path: ${{ steps.ccache-info.outputs.cache-dir }} + key: cpp-odbc-ccache-windows-x64-${{ hashFiles('cpp/**') }} + restore-keys: cpp-odbc-ccache-windows-x64- + - name: Checkout vcpkg + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + path: vcpkg + repository: microsoft/vcpkg + - name: Bootstrap vcpkg + run: | + vcpkg\bootstrap-vcpkg.bat + $VCPKG_ROOT = $(Resolve-Path -LiteralPath "vcpkg").ToString() + Write-Output ${VCPKG_ROOT} | ` + Out-File -FilePath ${Env:GITHUB_PATH} -Encoding utf8 -Append + Write-Output "VCPKG_ROOT=${VCPKG_ROOT}" | ` + Out-File -FilePath ${Env:GITHUB_ENV} -Encoding utf8 -Append + - name: Setup NuGet credentials for vcpkg caching + shell: bash + run: | + $(vcpkg fetch nuget | tail -n 1) \ + sources add \ + -source "https://nuget.pkg.github.com/$GITHUB_REPOSITORY_OWNER/index.json" \ + -storepasswordincleartext \ + -name "GitHub" \ + -username "$GITHUB_REPOSITORY_OWNER" \ + -password "${{ secrets.GITHUB_TOKEN }}" + $(vcpkg fetch nuget | tail -n 1) \ + setapikey "${{ secrets.GITHUB_TOKEN }}" \ + -source "https://nuget.pkg.github.com/$GITHUB_REPOSITORY_OWNER/index.json" + - name: Build + shell: cmd + run: | + set VCPKG_ROOT_KEEP=%VCPKG_ROOT% + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 + set VCPKG_ROOT=%VCPKG_ROOT_KEEP% + bash -c "ci/scripts/cpp_build.sh $(pwd) $(pwd)/build" + - name: Register Flight SQL ODBC Driver + shell: cmd + run: | + call "cpp\src\arrow\flight\sql\odbc\tests\install_odbc.cmd" ${{ github.workspace }}\build\cpp\%ARROW_BUILD_TYPE%\arrow_flight_sql_odbc.dll + # GH-48270 TODO: Resolve segementation fault during Arrow library unload + # GH-48269 TODO: Enable Flight & Flight SQL testing in MSVC CI + # TODO: enable ODBC tests after GH-48270 and GH-48269 are resolved. + # - name: Test + # shell: cmd + # run: | + # set VCPKG_ROOT_KEEP=%VCPKG_ROOT% + # call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 + # # For ORC + # set TZDIR=${{ steps.setup-msys2.outputs.msys2-location }}\usr\share\zoneinfo + + # # Convert VCPKG Windows path to MSYS path + # for /f "usebackq delims=" %%I in (`bash -c "cygpath -u \"$VCPKG_ROOT_KEEP\""` ) do set VCPKG_ROOT=%%I + + # bash -c "ci/scripts/cpp_test.sh $(pwd) $(pwd)/build" + + - name: Install WiX Toolset + shell: pwsh + run: | + Invoke-WebRequest -Uri https://github.com/wixtoolset/wix/releases/download/v6.0.0/wix-cli-x64.msi -OutFile wix-cli-x64.msi + Start-Process -FilePath wix-cli-x64.msi -ArgumentList '/quiet', 'Include_freethreaded=1' -Wait + echo "C:\Program Files\WiX Toolset v6.0\bin\" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Build MSI ODBC installer + shell: pwsh + run: | + # Verify WiX version + wix --version + cd "${{ github.workspace }}\build\cpp" + cpack + - name: Upload the artifacts to the job + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: flight-sql-odbc-msi-installer + path: ${{ github.workspace }}\build\cpp\Apache Arrow Flight SQL ODBC-1.0.0-win64.msi + macos: + name: ${{ matrix.architecture }} macOS ${{ matrix.macos-version }} C++ + runs-on: macos-${{ matrix.macos-version }} + if: ${{ !contains(github.event.pull_request.title, 'WIP') }} + timeout-minutes: 75 + strategy: + fail-fast: false + matrix: + include: + - architecture: AMD64 + macos-version: "15-intel" + - architecture: ARM64 + macos-version: "14" + env: + ARROW_BUILD_TESTS: ON + ARROW_FLIGHT_SQL_ODBC: ON + ARROW_HOME: /tmp/local + steps: + - name: Checkout Arrow + uses: actions/checkout@v6.0.0 + with: + fetch-depth: 0 + submodules: recursive + - name: Install Dependencies + run: | + brew bundle --file=cpp/Brewfile + export LIBIODBC_DIR="$(brew --cellar libiodbc)/$(brew list --versions libiodbc | awk '{print $2}')" + echo ODBC_INCLUDE_DIR="$LIBIODBC_DIR/include" >> $GITHUB_ENV + echo ODBC_LIB_DIR="$LIBIODBC_DIR/lib" >> $GITHUB_ENV + - name: Setup ccache + run: | + ci/scripts/ccache_setup.sh + - name: ccache info + id: ccache-info + run: | + echo "cache-dir=$(ccache --get-config cache_dir)" >> $GITHUB_OUTPUT + - name: Cache ccache + uses: actions/cache@v4 + with: + path: ${{ steps.ccache-info.outputs.cache-dir }} + key: cpp-ccache-macos-${{ matrix.macos-version }}-${{ hashFiles('cpp/**') }} + restore-keys: cpp-ccache-macos-${{ matrix.macos-version }}- + - name: Build + run: | + # Homebrew uses /usr/local as prefix. So packages + # installed by Homebrew also use /usr/local/include. We + # want to include headers for packages installed by + # Homebrew as system headers to ignore warnings in them. + # But "-isystem /usr/local/include" isn't used by CMake + # because /usr/local/include is marked as the default + # include path. So we disable -Werror to avoid build error + # by warnings from packages installed by Homebrew. + export BUILD_WARNING_LEVEL=PRODUCTION + ci/scripts/cpp_build.sh $(pwd) $(pwd)/build + - name: Register Flight SQL ODBC Driver + run: | + chmod +x cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc.sh + sudo cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc.sh $(pwd)/build/cpp/debug/libarrow_flight_sql_odbc.dylib + - name: Test + shell: bash + run: | + ci/scripts/cpp_test.sh $(pwd) $(pwd)/build diff --git a/.gitignore b/.gitignore index 8354aa8f816..64c713a74ed 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ dependency-reduced-pom.xml MANIFEST compile_commands.json build.ninja +build*/ # Generated Visual Studio files *.vcxproj @@ -107,3 +108,6 @@ java/.mvn/.develocity/ # rat filtered_rat.txt rat.txt + +# rc +*.rc diff --git a/ci/scripts/cpp_build.sh b/ci/scripts/cpp_build.sh index 2f02f8c1496..c938a74f495 100755 --- a/ci/scripts/cpp_build.sh +++ b/ci/scripts/cpp_build.sh @@ -213,6 +213,7 @@ else -DARROW_FLIGHT=${ARROW_FLIGHT:-OFF} \ -DARROW_FLIGHT_SQL=${ARROW_FLIGHT_SQL:-OFF} \ -DARROW_FLIGHT_SQL_ODBC=${ARROW_FLIGHT_SQL_ODBC:-OFF} \ + -DARROW_FLIGHT_SQL_ODBC_INSTALLER=${ARROW_FLIGHT_SQL_ODBC_INSTALLER:-OFF} \ -DARROW_FUZZING=${ARROW_FUZZING:-OFF} \ -DARROW_GANDIVA_PC_CXX_FLAGS=${ARROW_GANDIVA_PC_CXX_FLAGS:-} \ -DARROW_GANDIVA=${ARROW_GANDIVA:-OFF} \ @@ -259,7 +260,7 @@ else -DCMAKE_BUILD_TYPE=${ARROW_BUILD_TYPE:-debug} \ -DCMAKE_VERBOSE_MAKEFILE=${CMAKE_VERBOSE_MAKEFILE:-OFF} \ -DCMAKE_C_FLAGS="${CFLAGS:-}" \ - -DCMAKE_CXX_FLAGS="${CXXFLAGS:-}" \ + -DCMAKE_CXX_FLAGS="${CXXFLAGS:-} -I${ODBC_INCLUDE_DIR:-} -L${ODBC_LIB_DIR:-}" \ -DCMAKE_CXX_STANDARD="${CMAKE_CXX_STANDARD:-17}" \ -DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR:-lib} \ -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX:-${ARROW_HOME}} \ @@ -270,6 +271,7 @@ else -DgRPC_SOURCE=${gRPC_SOURCE:-} \ -DGTest_SOURCE=${GTest_SOURCE:-} \ -Dlz4_SOURCE=${lz4_SOURCE:-} \ + -DODBC_INCLUDE_DIR="${ODBC_INCLUDE_DIR:-}" \ -Dopentelemetry-cpp_SOURCE=${opentelemetry_cpp_SOURCE:-} \ -DORC_SOURCE=${ORC_SOURCE:-} \ -DPARQUET_BUILD_EXAMPLES=${PARQUET_BUILD_EXAMPLES:-OFF} \ diff --git a/ci/scripts/cpp_test.sh b/ci/scripts/cpp_test.sh old mode 100755 new mode 100644 index 0ad59bc308f..515d981ca81 --- a/ci/scripts/cpp_test.sh +++ b/ci/scripts/cpp_test.sh @@ -61,6 +61,7 @@ case "$(uname)" in n_jobs=$(sysctl -n hw.ncpu) # TODO: https://github.com/apache/arrow/issues/40410 exclude_tests+=("arrow-s3fs-test") + exclude_tests+=("arrow-flight-sql-odbc-test") ;; MINGW*) n_jobs=${NUMBER_OF_PROCESSORS:-1} diff --git a/cpp/Brewfile b/cpp/Brewfile index 4c42607568c..811712516bf 100644 --- a/cpp/Brewfile +++ b/cpp/Brewfile @@ -28,6 +28,7 @@ brew "git" brew "glog" brew "googletest" brew "grpc" +brew "libiodbc" brew "llvm" brew "lz4" brew "mimalloc" diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index c9f026f926a..aeffedab03e 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -720,9 +720,13 @@ endif() install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE.txt ${CMAKE_CURRENT_SOURCE_DIR}/../NOTICE.txt - ${CMAKE_CURRENT_SOURCE_DIR}/README.md DESTINATION "${ARROW_DOC_DIR}") + ${CMAKE_CURRENT_SOURCE_DIR}/README.md + DESTINATION "${ARROW_DOC_DIR}" + COMPONENT arrow_doc) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/gdb_arrow.py DESTINATION "${ARROW_GDB_DIR}") +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/gdb_arrow.py + DESTINATION "${ARROW_GDB_DIR}" + COMPONENT arrow_gdb) # # Validate and print out Arrow configuration options diff --git a/cpp/CMakePresets.json b/cpp/CMakePresets.json index e6c2e7e43a6..c3499f6b006 100644 --- a/cpp/CMakePresets.json +++ b/cpp/CMakePresets.json @@ -180,6 +180,7 @@ "ARROW_BUILD_EXAMPLES": "ON", "ARROW_BUILD_UTILITIES": "ON", "ARROW_FLIGHT_SQL_ODBC": "ON", + "ARROW_FLIGHT_SQL_ODBC_INSTALLER": "ON", "ARROW_TENSORFLOW": "ON", "PARQUET_BUILD_EXAMPLES": "ON", "PARQUET_BUILD_EXECUTABLES": "ON" @@ -314,6 +315,17 @@ "displayName": "Debug build with tests and Flight SQL", "cacheVariables": {} }, + { + "name": "ninja-debug-flight-sql-odbc", + "inherits": [ + "features-flight-sql", + "base-debug" + ], + "displayName": "Debug build with tests and Flight SQL ODBC", + "cacheVariables": { + "ARROW_FLIGHT_SQL_ODBC": "ON" + } + }, { "name": "ninja-debug-gandiva", "inherits": [ @@ -510,6 +522,17 @@ "displayName": "Release build with Flight SQL", "cacheVariables": {} }, + { + "name": "ninja-release-flight-sql-odbc", + "inherits": [ + "features-flight-sql", + "base-release" + ], + "displayName": "Release build with Flight SQL ODBC", + "cacheVariables": { + "ARROW_FLIGHT_SQL_ODBC": "ON" + } + }, { "name": "ninja-release-gandiva", "inherits": [ diff --git a/cpp/cmake_modules/BuildUtils.cmake b/cpp/cmake_modules/BuildUtils.cmake index db760400f7c..305546572c4 100644 --- a/cpp/cmake_modules/BuildUtils.cmake +++ b/cpp/cmake_modules/BuildUtils.cmake @@ -178,10 +178,12 @@ function(arrow_install_cmake_package PACKAGE_NAME EXPORT_NAME) write_basic_package_version_file("${BUILT_CONFIG_VERSION_CMAKE}" COMPATIBILITY SameMajorVersion) install(FILES "${BUILT_CONFIG_CMAKE}" "${BUILT_CONFIG_VERSION_CMAKE}" - DESTINATION "${ARROW_CMAKE_DIR}/${PACKAGE_NAME}") + DESTINATION "${ARROW_CMAKE_DIR}/${PACKAGE_NAME}" + COMPONENT config_cmake_file) set(TARGETS_CMAKE "${PACKAGE_NAME}Targets.cmake") install(EXPORT ${EXPORT_NAME} DESTINATION "${ARROW_CMAKE_DIR}/${PACKAGE_NAME}" + COMPONENT config_cmake_export NAMESPACE "${PACKAGE_NAME}::" FILE "${TARGETS_CMAKE}") endfunction() @@ -403,8 +405,11 @@ function(ADD_ARROW_LIB LIB_NAME) install(TARGETS ${LIB_NAME}_shared ${INSTALL_IS_OPTIONAL} EXPORT ${LIB_NAME}_targets ARCHIVE DESTINATION ${INSTALL_ARCHIVE_DIR} + COMPONENT ${LIB_NAME}_shared_archive LIBRARY DESTINATION ${INSTALL_LIBRARY_DIR} + COMPONENT ${LIB_NAME}_shared_library RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} + COMPONENT ${LIB_NAME}_shared_runtime INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endif() @@ -471,8 +476,11 @@ function(ADD_ARROW_LIB LIB_NAME) install(TARGETS ${LIB_NAME}_static ${INSTALL_IS_OPTIONAL} EXPORT ${LIB_NAME}_targets ARCHIVE DESTINATION ${INSTALL_ARCHIVE_DIR} + COMPONENT ${LIB_NAME}_static_library LIBRARY DESTINATION ${INSTALL_LIBRARY_DIR} + COMPONENT ${LIB_NAME}_static_library RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} + COMPONENT ${LIB_NAME}_static_library INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endif() @@ -934,7 +942,9 @@ function(ARROW_INSTALL_ALL_HEADERS PATH) endif() list(APPEND PUBLIC_HEADERS ${HEADER}) endforeach() - install(FILES ${PUBLIC_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PATH}") + install(FILES ${PUBLIC_HEADERS} + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PATH}" + COMPONENT ${HEADER}_header) endfunction() function(ARROW_ADD_PKG_CONFIG MODULE) @@ -944,7 +954,8 @@ function(ARROW_ADD_PKG_CONFIG MODULE) OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/$/${MODULE}.pc" INPUT "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.pc.generate.in") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/$/${MODULE}.pc" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig/") + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig/" + COMPONENT ${MODULE}_pkg_config) endfunction() # Implementations of lisp "car" and "cdr" functions diff --git a/cpp/cmake_modules/DefineOptions.cmake b/cpp/cmake_modules/DefineOptions.cmake index 0f6674c7143..5d34ff50e35 100644 --- a/cpp/cmake_modules/DefineOptions.cmake +++ b/cpp/cmake_modules/DefineOptions.cmake @@ -107,8 +107,8 @@ macro(tsort_bool_option_dependencies) endmacro() macro(resolve_option_dependencies) - # Arrow Flight SQL ODBC is available only for Windows for now. - if(NOT WIN32) + # Arrow Flight SQL ODBC is available only for Windows and macOS for now. + if(NOT WIN32 AND NOT APPLE) set(ARROW_FLIGHT_SQL_ODBC OFF) endif() if(MSVC_TOOLCHAIN) diff --git a/cpp/cmake_modules/SetupCxxFlags.cmake b/cpp/cmake_modules/SetupCxxFlags.cmake index 3c172aebdf8..e0d1fa0dcca 100644 --- a/cpp/cmake_modules/SetupCxxFlags.cmake +++ b/cpp/cmake_modules/SetupCxxFlags.cmake @@ -186,6 +186,7 @@ if(WIN32) # # ARROW-2986: Without /EHsc we get C4530 warning set(CXX_COMMON_FLAGS "/W3 /EHsc") + string(APPEND CMAKE_CXX_FLAGS " /EHsc") endif() # Disable C5105 (macro expansion producing 'defined' has undefined diff --git a/cpp/cmake_modules/ThirdpartyToolchain.cmake b/cpp/cmake_modules/ThirdpartyToolchain.cmake index aa5c426aff3..4711b3b59a6 100644 --- a/cpp/cmake_modules/ThirdpartyToolchain.cmake +++ b/cpp/cmake_modules/ThirdpartyToolchain.cmake @@ -236,7 +236,8 @@ function(provide_cmake_module MODULE_NAME ARROW_CMAKE_PACKAGE_NAME) message(STATUS "Providing CMake module for ${MODULE_NAME} as part of ${ARROW_CMAKE_PACKAGE_NAME} CMake package" ) install(FILES "${module}" - DESTINATION "${ARROW_CMAKE_DIR}/${ARROW_CMAKE_PACKAGE_NAME}") + DESTINATION "${ARROW_CMAKE_DIR}/${ARROW_CMAKE_PACKAGE_NAME}" + COMPONENT ${MODULE_NAME}_module) endif() endfunction() @@ -2442,20 +2443,22 @@ function(build_gtest) endforeach() install(DIRECTORY "${googletest_SOURCE_DIR}/googlemock/include/" "${googletest_SOURCE_DIR}/googletest/include/" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + COMPONENT gtest_dir) add_library(arrow::GTest::gtest_headers INTERFACE IMPORTED) target_include_directories(arrow::GTest::gtest_headers INTERFACE "${googletest_SOURCE_DIR}/googlemock/include/" "${googletest_SOURCE_DIR}/googletest/include/") install(TARGETS gmock gmock_main gtest gtest_main EXPORT arrow_testing_targets - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT gtest_runtime + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT gtest_archive + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT gtest_library) if(MSVC) install(FILES $ $ $ $ DESTINATION "${CMAKE_INSTALL_BINDIR}" + COMPONENT gtest_pdb OPTIONAL) endif() add_library(arrow::GTest::gmock ALIAS gmock) diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt index deb6db3cdd3..f7143067613 100644 --- a/cpp/src/arrow/CMakeLists.txt +++ b/cpp/src/arrow/CMakeLists.txt @@ -349,7 +349,8 @@ string(REPLACE "${CMAKE_BINARY_DIR}" "" REDACTED_CXX_FLAGS configure_file("util/config.h.cmake" "util/config.h" ESCAPE_QUOTES) configure_file("util/config_internal.h.cmake" "util/config_internal.h" ESCAPE_QUOTES) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/util/config.h" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/arrow/util") + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/arrow/util" + COMPONENT arrow_config) set(ARROW_SRCS builder.cc @@ -1051,7 +1052,8 @@ if(ARROW_BUILD_BUNDLED_DEPENDENCIES) get_target_property(arrow_bundled_dependencies_path arrow_bundled_dependencies IMPORTED_LOCATION) install(FILES ${arrow_bundled_dependencies_path} ${INSTALL_IS_OPTIONAL} - DESTINATION ${CMAKE_INSTALL_LIBDIR}) + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT arrow_bundled_dependencies) string(PREPEND ARROW_PC_LIBS_PRIVATE " -larrow_bundled_dependencies") list(INSERT ARROW_STATIC_INSTALL_INTERFACE_LIBS 0 "Arrow::arrow_bundled_dependencies") endif() @@ -1168,6 +1170,7 @@ if(ARROW_BUILD_SHARED AND NOT WIN32) if(ARROW_GDB_AUTO_LOAD_LIBARROW_GDB_INSTALL) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libarrow_gdb.py" DESTINATION "${ARROW_GDB_AUTO_LOAD_LIBARROW_GDB_DIR}" + COMPONENT arrow_gdb RENAME "$-gdb.py") endif() endif() @@ -1231,11 +1234,13 @@ arrow_install_all_headers("arrow") config_summary_cmake_setters("${CMAKE_CURRENT_BINARY_DIR}/ArrowOptions.cmake") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ArrowOptions.cmake - DESTINATION "${ARROW_CMAKE_DIR}/Arrow") + DESTINATION "${ARROW_CMAKE_DIR}/Arrow" + COMPONENT arrow_options_cmake) # For backward compatibility for find_package(arrow) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/arrow-config.cmake - DESTINATION "${ARROW_CMAKE_DIR}/Arrow") + DESTINATION "${ARROW_CMAKE_DIR}/Arrow" + COMPONENT arrow_config_cmake) # # Unit tests diff --git a/cpp/src/arrow/flight/sql/column_metadata.cc b/cpp/src/arrow/flight/sql/column_metadata.cc index 30f557084b2..8d2d2b4ddca 100644 --- a/cpp/src/arrow/flight/sql/column_metadata.cc +++ b/cpp/src/arrow/flight/sql/column_metadata.cc @@ -58,8 +58,15 @@ const char* ColumnMetadata::kIsSearchable = "ARROW:FLIGHT:SQL:IS_SEARCHABLE"; const char* ColumnMetadata::kRemarks = "ARROW:FLIGHT:SQL:REMARKS"; ColumnMetadata::ColumnMetadata( - std::shared_ptr metadata_map) - : metadata_map_(std::move(metadata_map)) {} + std::shared_ptr metadata_map) { + if (metadata_map) { + metadata_map_ = std::move(metadata_map); + } else { + std::shared_ptr empty_metadata_map( + new arrow::KeyValueMetadata); + metadata_map_ = std::move(empty_metadata_map); + } +} arrow::Result ColumnMetadata::GetCatalogName() const { return metadata_map_->Get(kCatalogName); diff --git a/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt index ac18a9bc7cd..338ee165187 100644 --- a/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt @@ -38,10 +38,29 @@ add_subdirectory(tests) arrow_install_all_headers("arrow/flight/sql/odbc") -set(ARROW_FLIGHT_SQL_ODBC_SRCS entry_points.cc odbc_api.cc) +# ODBC Release information +set(ODBC_PACKAGE_VERSION_MAJOR "1") +set(ODBC_PACKAGE_VERSION_MINOR "0") +set(ODBC_PACKAGE_VERSION_PATCH "0") +set(ODBC_PACKAGE_NAME "Apache Arrow Flight SQL ODBC") +set(ODBC_PACKAGE_VENDOR "Apache Arrow") + +# Compile entry_points.cc before odbc_api.cc due to conflict from sql.h and flight/types.h +set(ARROW_FLIGHT_SQL_ODBC_SRCS odbc_api.cc entry_points.cc) if(WIN32) - list(APPEND ARROW_FLIGHT_SQL_ODBC_SRCS odbc.def) + set(VER_FILEVERSION + "${ODBC_PACKAGE_VERSION_MAJOR},${ODBC_PACKAGE_VERSION_MINOR},${ODBC_PACKAGE_VERSION_PATCH},0" + ) + set(VER_FILEVERSION_STR + ${ODBC_PACKAGE_VERSION_MAJOR}.${ODBC_PACKAGE_VERSION_MINOR}.${ODBC_PACKAGE_VERSION_PATCH} + ) + set(VER_COMPANYNAME_STR ${ODBC_PACKAGE_VENDOR}) + set(VER_PRODUCTNAME_STR ${ODBC_PACKAGE_NAME}) + + configure_file("install/windows/versioninfo.rc.in" "install/versioninfo.rc" @ONLY) + + list(APPEND ARROW_FLIGHT_SQL_ODBC_SRCS odbc.def install/versioninfo.rc) endif() add_arrow_lib(arrow_flight_sql_odbc @@ -75,3 +94,77 @@ add_arrow_lib(arrow_flight_sql_odbc foreach(LIB_TARGET ${ARROW_FLIGHT_SQL_ODBC_LIBRARIES}) target_compile_definitions(${LIB_TARGET} PRIVATE ARROW_FLIGHT_SQL_ODBC_EXPORTING) endforeach() + +# Construct ODBC Windows installer. Only Release installer is supported +if(ARROW_FLIGHT_SQL_ODBC_INSTALLER) + + include(InstallRequiredSystemLibraries) + + set(CPACK_RESOURCE_FILE_LICENSE + "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../LICENSE.txt") + # Tentative version 1.0.0 + set(CPACK_PACKAGE_VERSION_MAJOR ${ODBC_PACKAGE_VERSION_MAJOR}) + set(CPACK_PACKAGE_VERSION_MINOR ${ODBC_PACKAGE_VERSION_MINOR}) + set(CPACK_PACKAGE_VERSION_PATCH ${ODBC_PACKAGE_VERSION_PATCH}) + + set(CPACK_PACKAGE_NAME ${ODBC_PACKAGE_NAME}) + set(CPACK_PACKAGE_VENDOR ${ODBC_PACKAGE_VENDOR}) + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Apache Arrow Flight SQL ODBC Driver") + set(CPACK_PACKAGE_CONTACT "#GH-47787 TODO arrow maintainers") + + # GH-47876 TODO: set up `flight_sql_odbc_lib` component for macOS Installer + # GH-47877 TODO: set up `flight_sql_odbc_lib` component for Linux Installer + if(WIN32) + install(DIRECTORY "${BUILD_OUTPUT_ROOT_DIRECTORY}${CMAKE_BUILD_TYPE}/" + DESTINATION bin + COMPONENT flight_sql_odbc_lib + FILES_MATCHING + # Use regex for dll name patterns with versions + PATTERN "abseil_dll.dll" + PATTERN "arrow.dll" + PATTERN "arrow_compute.dll" + PATTERN "arrow_flight.dll" + PATTERN "arrow_flight_sql.dll" + PATTERN "arrow_flight_sql_odbc.dll" + PATTERN "boost_locale*.dll" + PATTERN "cares.dll" + PATTERN "libcrypto*.dll" + PATTERN "libprotobuf.dll" + PATTERN "libssl*.dll" + PATTERN "re2.dll" + PATTERN "utf8proc.dll" + PATTERN "zlib1.dll") + + set(CPACK_WIX_EXTRA_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/install/windows/arrow-flight-sql-odbc.wxs") + set(CPACK_WIX_PATCH_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/install/windows/arrow-flight-sql-odbc-patch.xml") + + set(CPACK_WIX_UI_BANNER + "${CMAKE_CURRENT_SOURCE_DIR}/install/windows/arrow-wix-banner.bmp") + endif() + + get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) + set(CPACK_COMPONENTS_ALL Unspecified) + list(APPEND CPACK_COMPONENTS_ALL "flight_sql_odbc_lib") + + if(WIN32) + # WiX msi installer on Windows + # CPack is compatible with WiX V.5 and V.6 + set(CPACK_GENERATOR "WIX") + set(CPACK_WIX_VERSION 4) + + # Upgrade GUID is required to be unchanged for ODBC installer to upgrade + set(CPACK_WIX_UPGRADE_GUID "DBF27A18-F8BF-423F-9E3A-957414D52C4B") + set(CPACK_WIX_PRODUCT_GUID "279D087B-93B5-4DC3-BA69-BCF485022A26") + endif() + # GH-47876 TODO: create macOS Installer using cpack + # GH-47877 TODO: create Linux Installer using cpack + + # Load CPack after all CPACK* variables are set + include(CPack) + cpack_add_component(flight_sql_odbc_lib + DISPLAY_NAME "ODBC library" + DESCRIPTION "ODBC library bin, required to install" + REQUIRED) +endif() diff --git a/cpp/src/arrow/flight/sql/odbc/README b/cpp/src/arrow/flight/sql/odbc/README new file mode 100644 index 00000000000..120a92950f9 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/README @@ -0,0 +1,83 @@ + + +## 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\tests\install_odbc.cmd \cpp\build\< release | debug >\< Release | Debug>\arrow_flight_sql_odbc.dll` + Example command for reference: + `.\cpp\src\arrow\flight\sql\odbc\tests\install_odbc.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. + +## Steps to Generate Windows Installer +1. Build with `ARROW_FLIGHT_SQL_ODBC=ON` and `ARROW_FLIGHT_SQL_ODBC_INSTALLER=ON`. +2. `cd` to `build` folder. +3. Run `cpack`. + +If the generation is successful, you will find `Apache Arrow Flight SQL ODBC--win64.msi` generated under the `build` folder. + +## Steps to Register the 64-bit Apache Arrow ODBC driver on macOS + +After the build succeeds, the ODBC DYLIB will be located in +`build\debug` for a debug build and `build\release` for a release build. + +1. Open terminal shell. + +2. Register your ODBC DYLIB: + Need to replace with actual path to repository in the commands. + + i. `cd to repo.` + ii. `cd ` + iii. Give script permission to execute + `chmod +x cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc.sh` + iv. Run script with `sudo` to register your ODBC DYLIB as Apache Arrow Flight SQL ODBC Driver + `sudo cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc.sh /cpp/build/< release | debug >/libarrow_flight_sql_odbc.dylib` + Example command for reference: + `sudo cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc.sh /path/to/arrow/cpp/build/release/libarrow_flight_sql_odbc.dylib` + +If the registration is successful, then Apache Arrow Flight SQL ODBC Driver +should be shown at `~/Library/ODBC/odbcinst.ini` + +## Steps to Enable Logging +Arrow Flight SQL ODBC driver uses Arrow's internal logging framework. By default, the log messages are printed to the terminal. +1. Set environment variable `ARROW_ODBC_LOG_LEVEL` to any of the following valid values to enable logging. If `ARROW_ODBC_LOG_LEVEL` is set to a non-empty string that does not match any of the following values, `DEBUG` level is used by default. + +The characters are case-insensitive. +- TRACE +- DEBUG +- INFO +- WARNING +- ERROR +- FATAL + +The Windows ODBC driver currently does not support writing log files. `ARROW_USE_GLOG` is required to write log files, and `ARROW_USE_GLOG` is disabled on Windows platform since plasma using `glog` is not fully tested on windows. + +Note: GH-47670 running more than 1 tests with logging enabled is not fully supported. diff --git a/cpp/src/arrow/flight/sql/odbc/entry_points.cc b/cpp/src/arrow/flight/sql/odbc/entry_points.cc index 8e7c2e2be71..c03089ffcc6 100644 --- a/cpp/src/arrow/flight/sql/odbc/entry_points.cc +++ b/cpp/src/arrow/flight/sql/odbc/entry_points.cc @@ -83,6 +83,18 @@ SQLRETURN SQL_API SQLGetDiagRec(SQLSMALLINT handle_type, SQLHANDLE handle, buffer_length, text_length_ptr); } +#if defined(__APPLE__) +// macOS ODBC Driver Manager doesn't map SQLError to SQLGetDiagRec, so we need to +// implement SQLError for macOS. +// on Windows, SQLError mapping implemented by Driver Manager is preferred. +SQLRETURN SQL_API SQLError(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt, SQLWCHAR* sql_state, + SQLINTEGER* native_error_ptr, SQLWCHAR* message_text, + SQLSMALLINT buffer_length, SQLSMALLINT* text_length_ptr) { + return arrow::flight::sql::odbc::SQLError(env, conn, stmt, sql_state, native_error_ptr, + message_text, buffer_length, text_length_ptr); +} +#endif // __APPLE__ + SQLRETURN SQL_API SQLGetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER value_ptr, SQLINTEGER buffer_len, SQLINTEGER* str_len_ptr) { return arrow::flight::sql::odbc::SQLGetEnvAttr(env, attr, value_ptr, buffer_len, diff --git a/cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc.sh b/cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc.sh new file mode 100644 index 00000000000..0916bff0845 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# +# 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. + +# Used by macOS ODBC installer script and macOS ODBC testing + +ODBC_64BIT="$1" + +if [[ -z "$ODBC_64BIT" ]]; then + echo "error: 64-bit driver is not specified. Call format: install_odbc abs_path_to_64_bit_driver" + exit 1 +fi + +if [ ! -f $ODBC_64BIT ]; then + echo "64-bit driver can not be found: $ODBC_64BIT" + echo "Call format: install_odbc abs_path_to_64_bit_driver" + exit 1 +fi + +USER_ODBCINST_FILE="$HOME/Library/ODBC/odbcinst.ini" +DRIVER_NAME="Apache Arrow Flight SQL ODBC Driver" +DSN_NAME="Apache Arrow Flight SQL ODBC DSN" + +mkdir -p $HOME/Library/ODBC + +touch "$USER_ODBCINST_FILE" + +# Admin privilege is needed to add ODBC driver registration +if [ $EUID -ne 0 ]; then + echo "Please run this script with sudo" + exit 1 +fi + +if grep -q "^\[$DRIVER_NAME\]" "$USER_ODBCINST_FILE"; then + echo "Driver [$DRIVER_NAME] already exists in odbcinst.ini" +else + echo "Adding [$DRIVER_NAME] to odbcinst.ini..." + echo " +[$DRIVER_NAME] +Description=An ODBC Driver for Apache Arrow Flight SQL +Driver=$ODBC_64BIT +" >> "$USER_ODBCINST_FILE" +fi + +# Check if [ODBC Drivers] section exists +if grep -q '^\[ODBC Drivers\]' "$USER_ODBCINST_FILE"; then + # Section exists: check if driver entry exists + if ! grep -q "^${DRIVER_NAME}=" "$USER_ODBCINST_FILE"; then + # Driver entry does not exist, add under [ODBC Drivers] + sed -i '' "/^\[ODBC Drivers\]/a\\ +${DRIVER_NAME}=Installed +" "$USER_ODBCINST_FILE" + fi +else + # Section doesn't exist, append both section and driver entry at end + { + echo "" + echo "[ODBC Drivers]" + echo "${DRIVER_NAME}=Installed" + } >> "$USER_ODBCINST_FILE" +fi diff --git a/cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc_ini.sh b/cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc_ini.sh new file mode 100644 index 00000000000..cfc9729ed7e --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/mac/install_odbc_ini.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# +# 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. + +# GH-47876 TODO: create macOS ODBC Installer. +# Script for installing macOS ODBC driver, to be used for macOS installer. + +source_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +odbc_install_script="${source_dir}/install_odbc.sh" + +chmod +x "$odbc_install_script" +. "$odbc_install_script" /Library/Apache/ArrowFlightSQLODBC/lib/libarrow_flight_sql_odbc.dylib + +USER_ODBC_FILE="$HOME/Library/ODBC/odbc.ini" +DRIVER_NAME="Apache Arrow Flight SQL ODBC Driver" +DSN_NAME="Apache Arrow Flight SQL ODBC DSN" + +touch "$USER_ODBC_FILE" + +if [ $EUID -ne 0 ]; then + echo "Please run this script with sudo" + exit 1 +fi + +if grep -q "^\[$DSN_NAME\]" "$USER_ODBC_FILE"; then + echo "DSN [$DSN_NAME] already exists in $USER_ODBC_FILE" +else + echo "Adding [$DSN_NAME] to $USER_ODBC_FILE..." + cat >> "$USER_ODBC_FILE" < "${USER_ODBC_FILE}.tmp" && mv "${USER_ODBC_FILE}.tmp" "$USER_ODBC_FILE" + fi +else + # Section doesn't exist, append section and DSN entry at end + { + echo "" + echo "[ODBC Data Sources]" + echo "${DSN_NAME}=${DRIVER_NAME}" + } >> "$USER_ODBC_FILE" +fi + diff --git a/cpp/src/arrow/flight/sql/odbc/install/windows/arrow-flight-sql-odbc-patch.xml b/cpp/src/arrow/flight/sql/odbc/install/windows/arrow-flight-sql-odbc-patch.xml new file mode 100644 index 00000000000..f1a63ce5d3b --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/windows/arrow-flight-sql-odbc-patch.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/cpp/src/arrow/flight/sql/odbc/install/windows/arrow-flight-sql-odbc.wxs b/cpp/src/arrow/flight/sql/odbc/install/windows/arrow-flight-sql-odbc.wxs new file mode 100644 index 00000000000..bd0216aa766 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/windows/arrow-flight-sql-odbc.wxs @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/cpp/src/arrow/flight/sql/odbc/install/windows/arrow-wix-banner.bmp b/cpp/src/arrow/flight/sql/odbc/install/windows/arrow-wix-banner.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0c82036f4ecfc1e8c34fcb7eae56ccd7d9be4731 GIT binary patch literal 29930 zcmeI52V4}#7sr3t1q(I=v13e3j2bl(TZ$!WjK&zFu_V^S8ZqFAwFC|E{!r}%Ajo7vaq(cM!9n3 zP`-S5RH#q^6)RRmrAn1xV`GELl`F&6))sblcBoRN3heFeQMGDSRI64E)vH%WjT$vj zvt~`ys#OcMYuAQ@g9GZ+se{*EdkuB#*2U|uzm7NFcmwt7)x(=_zKOTqdJFaI*T>s$ zzl{bB8lYjrhG^8N5#D*{9W-v-7)_cqLDQy9@$S3tqFJ+MaCCIUd+)u6=FOXbzCAwv_+xbF&;cDg zc0{L6o$$#gpP+N+&iM4xPtm1I7j*5~6`y_f8M<}rhR;9$9NoKj#}{9GfgU}2;L9(+ zM9-c*(W_T4eD&2==-sWWR9Ho?u!4esvl*t~f&wrtq~4-XG) z-MST?o}SpYZ5y_4-;NzSc3|huo%r+5Ke21qF6`dD8-M-v7reZ@uxHO6?A^N;`}Xa_ z{{8!L;J^VKJa`a?4jsbb!-sL?$PpYpdKAZw9mDbC$8qAs37kB665ih4@bU41udgpo zojL_SKR=v4eHv%ZoPoc;KhB;#i-3Rt1O^7;+_`fI3JSvc^XGBl!UY5e2jk+!iwFq` zL1<_w!otFE>Cz=!zI++s;o-P)MHE-Me=YA0Ll<_wFGfAp!UA-$!C%A|5<=fTW}( zBqt{$B_#z9A3j8CYAPN*dW5vJG^D4eI;PH0#LZd*5^vN}*|WqORaJN;-mneM{YC`u-1TCfQkgeu>YHdXZ#8h4 zH|%l;E_jHAtmTbL7jn4? z!8;eFn61d4aLGYy;b*l1*ePxDK%FEv;B?Sz?@mi@ zXKLYe%s5k#HeU^>fGdM4D&Uo6yc#zh@bc%4Z$$u!&^yiqNyz0 z1i0XB-h54Jm*b+tYh!{}ao`$v-8rR=EL`w5(WsdVRKRT&2d;l&SCsLpfR{aIa6f{# zfjVA#;z@GylfS8iMsMM{IB?}BpttZcLF)VjZ?!fGHonuzX>6vPy*^IgL)^ z0*W|AvKZRl4ty5!xM|j^@d0o93D4fL3ksz5DGYJ@_VYet5!;`Ck&h*33Y$bsdwrIGRv2~Q< z@S|zpMv+lc1-z1iSKlTtC*#%VftL#8dC7q?(niI;fHm-_07+?iM{M0%V{*E`ZL zqccgcnjg~!74Y&3UV3IIp^R+L8V{`imb^S3NFqnXw%|38TpSolpTKRWuR@1W_KJNI znBKe*uJAqrIHptb1Yj+-&9kk5le{*|BwleWLE3lWB3)2*xs*P(Mi1Olao~F3B@_!6 zymcD<7b4EU7|J$@w9GA7=ABv}IK>N-d1uo@dKXU~0;)-EGL4=50MBV9arS2c(jMd? zR6&Kew23`y@p{Tv(zd8<;S^KlA)*DV0p0?m208c3ydlncPn|sqD0ZEf!+AT;o6DE?GF0XOxRb z7N7=30*l#Wd&OgO-i;j{8x^7bUfI4IIyyGt%RdlH6W()y<>}Ta7FU?u!+xNTQN;vr zBZa*Dzw~(OH)mwbSj$%hTv-MX9}Zjiix` zJhU~il%d=w!{RMyOojYF z4vnT2qP`b(=4LW>U*a9j7`^ctqQ4n2-lErqImIF+TKoZW7+iT0* z-QAohIeA}iDk zw~~=vmMf;`?aYT?^t^}4{ys$>K($y-xY>5VQW<8&F-mJX0^8(##QRWL59YW{re)ef zWOvS_I3>gGOEpdBhbV;J)m1z(z{@L8YP7N2At6_|JeAH!<&m2qA(4Eqe*OJS^Bt6t z7wKAZ64G95{xcfLb3Ri!o^AK^^m>k}x=wLtQ;pM;9hi$lj`T`ry^H1sF>bk^@b#`@Cdgb{j)L&=0;(OqHsO?3a z8oOh8IwOCcF@@bcI4_G$%V0Ty|HQt%wYL&sS6=*NEpM%L%TuNJk3hd5lb`aN5bJs5TfP7Ig4^+oUHA45rq>O!F;^E|5^rTK0Mq=1eVgjUd?lQ>>3W)m3#fA5Vm(ds zWtg9u5$`(swf6Xx^!zr&&4_mum3ueJUf8#hLd-fT*26YyJOy4kvEF@wtJJJB6#t)v MS>rLo%F*-te(sql_state) + << ", native_error_ptr: " << static_cast(native_error_ptr) + << ", message_text: " << static_cast(message_text) + << ", buffer_length: " << buffer_length + << ", text_length_ptr: " << static_cast(text_length_ptr); + + SQLSMALLINT handle_type; + SQLHANDLE handle; + + if (env) { + handle_type = SQL_HANDLE_ENV; + handle = static_cast(env); + } else if (conn) { + handle_type = SQL_HANDLE_DBC; + handle = static_cast(conn); + } else if (stmt) { + handle_type = SQL_HANDLE_STMT; + handle = static_cast(stmt); + } else { + return static_cast(SQL_INVALID_HANDLE); + } + + // Use the last record + SQLINTEGER diag_number; + SQLSMALLINT diag_number_length; + + SQLRETURN ret = arrow::flight::sql::odbc::SQLGetDiagField( + handle_type, handle, 0, SQL_DIAG_NUMBER, &diag_number, sizeof(SQLINTEGER), 0); + if (ret != SQL_SUCCESS) { + return ret; + } + + if (diag_number == 0) { + return SQL_NO_DATA; + } + + SQLSMALLINT rec_number = static_cast(diag_number); + + return arrow::flight::sql::odbc::SQLGetDiagRec( + handle_type, handle, rec_number, sql_state, native_error_ptr, message_text, + buffer_length, text_length_ptr); +} +#endif // __APPLE__ + inline bool IsValidStringFieldArgs(SQLPOINTER diag_info_ptr, SQLSMALLINT buffer_length, SQLSMALLINT* string_length_ptr, bool is_unicode) { const SQLSMALLINT char_size = is_unicode ? GetSqlWCharSize() : sizeof(char); @@ -536,7 +586,6 @@ SQLRETURN SQLGetDiagRec(SQLSMALLINT handle_type, SQLHANDLE handle, SQLSMALLINT r << ", message_text: " << static_cast(message_text) << ", buffer_length: " << buffer_length << ", text_length_ptr: " << static_cast(text_length_ptr); - using arrow::flight::sql::odbc::Diagnostics; using ODBC::GetStringAttribute; using ODBC::ODBCConnection; using ODBC::ODBCDescriptor; @@ -736,7 +785,6 @@ SQLRETURN SQLGetConnectAttr(SQLHDBC conn, SQLINTEGER attribute, SQLPOINTER value << ", attribute: " << attribute << ", value_ptr: " << value_ptr << ", buffer_length: " << buffer_length << ", string_length_ptr: " << static_cast(string_length_ptr); - using ODBC::ODBCConnection; return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() { @@ -855,7 +903,7 @@ SQLRETURN SQLDriverConnect(SQLHDBC conn, SQLHWND window_handle, } #else // Attempt connection without loading DSN window on macOS/Linux - connection->Connect(dsn, properties, missing_properties); + connection->Connect(dsn_value, properties, missing_properties); #endif // Copy connection string to out_connection_string after connection attempt return ODBC::GetStringAttribute(true, connection_string, false, out_connection_string, @@ -1066,8 +1114,30 @@ SQLRETURN SQLExtendedFetch(SQLHSTMT stmt, SQLUSMALLINT fetch_orientation, << ", row_count_ptr: " << static_cast(row_count_ptr) << ", row_status_array: " << static_cast(row_status_array); - // GH-47714 TODO: Implement SQLExtendedFetch - return SQL_INVALID_HANDLE; + + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + if (fetch_orientation != SQL_FETCH_NEXT) { + throw DriverException("Optional feature not supported.", "HYC00"); + } + // fetch_offset 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 row_set_size = statement->GetRowsetSize(); + ARROW_LOG(DEBUG) << "SQL_ROWSET_SIZE value for SQLExtendedFetch: " << row_set_size; + + if (statement->Fetch(static_cast(row_set_size), row_count_ptr, + row_status_array)) { + return SQL_SUCCESS; + } else { + // Reached the end of rowset + return SQL_NO_DATA; + } + }); } SQLRETURN SQLFetchScroll(SQLHSTMT stmt, SQLSMALLINT fetch_orientation, @@ -1272,17 +1342,151 @@ SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT record_number, << ", output_length: " << static_cast(output_length) << ", numeric_attribute_ptr: " << static_cast(numeric_attribute_ptr); - // GH-47721 TODO: Implement SQLColAttribute, pre-requisite requires SQLColumns - return SQL_INVALID_HANDLE; + + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + ODBCDescriptor* ird = statement->GetIRD(); + SQLINTEGER output_length_int; + switch (field_identifier) { + // Numeric attributes + // internal is SQLLEN, no conversion is needed + case SQL_DESC_DISPLAY_SIZE: + case SQL_DESC_OCTET_LENGTH: { + ird->GetField(record_number, field_identifier, numeric_attribute_ptr, + buffer_length, &output_length_int); + break; + } + // internal is SQLULEN, conversion is needed. + case SQL_COLUMN_LENGTH: // ODBC 2.0 + case SQL_DESC_LENGTH: { + SQLULEN temp; + ird->GetField(record_number, field_identifier, &temp, buffer_length, + &output_length_int); + if (numeric_attribute_ptr) { + *numeric_attribute_ptr = 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(record_number, field_identifier, &temp, buffer_length, + &output_length_int); + if (numeric_attribute_ptr) { + *numeric_attribute_ptr = 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(record_number, field_identifier, &temp, buffer_length, + &output_length_int); + if (numeric_attribute_ptr) { + *numeric_attribute_ptr = 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(record_number, field_identifier, character_attribute_ptr, + buffer_length, &output_length_int); + break; + default: + throw DriverException("Invalid descriptor field", "HY091"); + } + if (output_length) { + *output_length = static_cast(output_length_int); + } + return SQL_SUCCESS; + }); } SQLRETURN SQLGetTypeInfo(SQLHSTMT stmt, SQLSMALLINT data_type) { - // GH-47237 TODO: return SQL_PRED_CHAR and SQL_PRED_BASIC for + // GH-47237 return SQL_PRED_CHAR and SQL_PRED_BASIC for // appropriate data types in `SEARCHABLE` field ARROW_LOG(DEBUG) << "SQLGetTypeInfoW called with stmt: " << stmt << " data_type: " << data_type; - // GH-47722 TODO: Implement SQLGetTypeInfo - return SQL_INVALID_HANDLE; + + using ODBC::ODBCStatement; + return ODBC::ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + + switch (data_type) { + case SQL_ALL_TYPES: + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_BIT: + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_NUMERIC: + case SQL_DECIMAL: + case SQL_FLOAT: + case SQL_REAL: + case SQL_DOUBLE: + case SQL_GUID: + case SQL_DATE: + case SQL_TYPE_DATE: + case SQL_TIME: + case SQL_TYPE_TIME: + case SQL_TIMESTAMP: + case SQL_TYPE_TIMESTAMP: + case SQL_INTERVAL_DAY: + case SQL_INTERVAL_DAY_TO_HOUR: + case SQL_INTERVAL_DAY_TO_MINUTE: + case SQL_INTERVAL_DAY_TO_SECOND: + case SQL_INTERVAL_HOUR: + case SQL_INTERVAL_HOUR_TO_MINUTE: + case SQL_INTERVAL_HOUR_TO_SECOND: + case SQL_INTERVAL_MINUTE: + case SQL_INTERVAL_MINUTE_TO_SECOND: + case SQL_INTERVAL_SECOND: + case SQL_INTERVAL_YEAR: + case SQL_INTERVAL_YEAR_TO_MONTH: + case SQL_INTERVAL_MONTH: + statement->GetTypeInfo(data_type); + break; + default: + throw DriverException("Invalid SQL data type", "HY004"); + } + + return SQL_SUCCESS; + }); } SQLRETURN SQLNativeSql(SQLHDBC conn, SQLWCHAR* in_statement_text, @@ -1330,8 +1534,110 @@ SQLRETURN SQLDescribeCol(SQLHSTMT stmt, SQLUSMALLINT column_number, SQLWCHAR* co << ", decimal_digits_ptr: " << static_cast(decimal_digits_ptr) << ", nullable_ptr: " << static_cast(nullable_ptr); - // GH-47724 TODO: Implement SQLDescribeCol - return SQL_INVALID_HANDLE; + + using ODBC::ODBCDescriptor; + using ODBC::ODBCStatement; + + return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() { + ODBCStatement* statement = reinterpret_cast(stmt); + ODBCDescriptor* ird = statement->GetIRD(); + SQLINTEGER output_length_int; + SQLSMALLINT sql_type; + + // Column SQL Type + ird->GetField(column_number, SQL_DESC_CONCISE_TYPE, &sql_type, sizeof(SQLSMALLINT), + nullptr); + if (data_type_ptr) { + *data_type_ptr = sql_type; + } + + // Column Name + if (column_name || name_length_ptr) { + ird->GetField(column_number, SQL_DESC_NAME, column_name, buffer_length, + &output_length_int); + if (name_length_ptr) { + // returned length should be in characters + *name_length_ptr = + static_cast(output_length_int / GetSqlWCharSize()); + } + } + + // Column Size + if (column_size_ptr) { + switch (sql_type) { + // 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(column_number, SQL_DESC_PRECISION, column_size_ptr, + sizeof(SQLULEN), nullptr); + break; + } + + default: { + ird->GetField(column_number, SQL_DESC_LENGTH, column_size_ptr, sizeof(SQLULEN), + nullptr); + } + } + } + + // Column Decimal Digits + if (decimal_digits_ptr) { + switch (sql_type) { + // All exact numeric types + case SQL_TINYINT: + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIGINT: + case SQL_DECIMAL: + case SQL_NUMERIC: { + ird->GetField(column_number, SQL_DESC_SCALE, decimal_digits_ptr, + sizeof(SQLULEN), nullptr); + break; + } + + // All datetime types (ODBC2) + case SQL_DATE: + case SQL_TIME: + case SQL_TIMESTAMP: + // All datetime types (ODBC3) + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TYPE_TIMESTAMP: + // 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(column_number, SQL_DESC_PRECISION, decimal_digits_ptr, + sizeof(SQLULEN), nullptr); + break; + } + + default: { + // All character and binary types + // SQL_BIT + // All approximate numeric types + // All interval types with no seconds component + *decimal_digits_ptr = static_cast(0); + } + } + } + + // Column Nullable + if (nullable_ptr) { + ird->GetField(column_number, SQL_DESC_NULLABLE, nullable_ptr, sizeof(SQLSMALLINT), + nullptr); + } + + return SQL_SUCCESS; + }); } } // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api_internal.h b/cpp/src/arrow/flight/sql/odbc/odbc_api_internal.h index 4fea8569acb..f9d8d887cb8 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api_internal.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api_internal.h @@ -31,6 +31,12 @@ namespace arrow::flight::sql::odbc { SQLHANDLE* result); [[nodiscard]] SQLRETURN SQLFreeHandle(SQLSMALLINT type, SQLHANDLE handle); [[nodiscard]] SQLRETURN SQLFreeStmt(SQLHSTMT stmt, SQLUSMALLINT option); +#if defined(__APPLE__) +[[nodiscard]] SQLRETURN SQLError(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt, + SQLWCHAR* sql_state, SQLINTEGER* native_error_ptr, + SQLWCHAR* message_text, SQLSMALLINT buffer_length, + SQLSMALLINT* text_length_ptr); +#endif // __APPLE__ [[nodiscard]] SQLRETURN SQLGetDiagField(SQLSMALLINT handle_type, SQLHANDLE handle, SQLSMALLINT rec_number, SQLSMALLINT diag_identifier, diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/odbc_impl/CMakeLists.txt index 2fe9c41e3ce..22531d9638e 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/CMakeLists.txt @@ -45,10 +45,10 @@ add_library(arrow_odbc_spi_impl config/connection_string_parser.h diagnostics.cc diagnostics.h - error_codes.h encoding.cc encoding.h encoding_utils.h + error_codes.h exceptions.cc exceptions.h flight_sql_auth_method.cc diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/binary_array_accessor.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/binary_array_accessor.cc index e4625ace370..939264bedb7 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/binary_array_accessor.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/binary_array_accessor.cc @@ -19,6 +19,7 @@ #include #include +#include #include "arrow/array.h" namespace arrow::flight::sql::odbc { @@ -39,7 +40,7 @@ inline RowStatus MoveSingleCellToBinaryBuffer(ColumnBinding* binding, BinaryArra auto* byte_buffer = static_cast(binding->buffer) + i * binding->buffer_length; - memcpy(byte_buffer, ((char*)value) + value_offset, value_length); + std::memcpy(byte_buffer, ((char*)value) + value_offset, value_length); if (remaining_length > binding->buffer_length) { result = RowStatus_SUCCESS_WITH_INFO; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/binary_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/binary_array_accessor_test.cc index 39d03692da6..423870eb3be 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/binary_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/binary_array_accessor_test.cc @@ -18,11 +18,12 @@ #include "arrow/flight/sql/odbc/odbc_impl/accessors/binary_array_accessor.h" #include "arrow/testing/builder.h" #include "arrow/testing/gtest_util.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { -TEST(BinaryArrayAccessor, Test_CDataType_BINARY_Basic) { +TEST(BinaryArrayAccessor, TestCDataTypeBinaryBasic) { std::vector values = {"foo", "barx", "baz123"}; std::shared_ptr array; ArrayFromVector(values, &array); @@ -53,7 +54,7 @@ TEST(BinaryArrayAccessor, Test_CDataType_BINARY_Basic) { } } -TEST(BinaryArrayAccessor, Test_CDataType_BINARY_Truncation) { +TEST(BinaryArrayAccessor, TestCDataTypeBinaryTruncation) { std::vector values = {"ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF"}; std::shared_ptr array; ArrayFromVector(values, &array); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/boolean_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/boolean_array_accessor_test.cc index 9b17a904598..b3f402dd7c1 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/boolean_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/boolean_array_accessor_test.cc @@ -17,11 +17,12 @@ #include "arrow/flight/sql/odbc/odbc_impl/accessors/boolean_array_accessor.h" #include "arrow/testing/builder.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { -TEST(BooleanArrayFlightSqlAccessor, Test_BooleanArray_CDataType_BIT) { +TEST(BooleanArrayFlightSqlAccessor, TestBooleanArrayCDataTypeBit) { const std::vector values = {true, false, true}; std::shared_ptr array; ArrayFromVector(values, &array); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/common.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/common.h index 0a79bc39dfb..45f88b50fb8 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/common.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/common.h @@ -19,6 +19,7 @@ #include #include +#include #include "arrow/array.h" #include "arrow/flight/sql/odbc/odbc_impl/accessors/types.h" #include "arrow/flight/sql/odbc/odbc_impl/diagnostics.h" @@ -42,7 +43,7 @@ inline size_t CopyFromArrayValuesToBinding(ARRAY_TYPE* array, ColumnBinding* bin } } } else { - // Duplicate this loop to avoid null checks within the loop. + // Duplicate above for-loop to exit early when null value is found for (int64_t i = starting_row; i < starting_row + cells; ++i) { if (array->IsNull(i)) { throw NullWithoutIndicatorException(); @@ -54,7 +55,7 @@ inline size_t CopyFromArrayValuesToBinding(ARRAY_TYPE* array, ColumnBinding* bin // Note that the array should already have been sliced down to the same number // of elements in the ODBC data array by the point in which this function is called. const auto* values = array->raw_values(); - memcpy(binding->buffer, &values[starting_row], element_size * cells); + std::memcpy(binding->buffer, &values[starting_row], element_size * cells); return cells; } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/date_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/date_array_accessor_test.cc index 03716e2477a..a482a838101 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/date_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/date_array_accessor_test.cc @@ -20,11 +20,12 @@ #include "arrow/flight/sql/odbc/odbc_impl/accessors/boolean_array_accessor.h" #include "arrow/flight/sql/odbc/odbc_impl/calendar_utils.h" #include "arrow/testing/builder.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { -TEST(DateArrayAccessor, Test_Date32Array_CDataType_DATE) { +TEST(DateArrayAccessor, TestDate32ArrayCDataTypeDate) { std::vector values = {7589, 12320, 18980, 19095, -1, 0}; std::vector expected = { {1990, 10, 12}, {2003, 9, 25}, {2021, 12, 19}, @@ -57,7 +58,7 @@ TEST(DateArrayAccessor, Test_Date32Array_CDataType_DATE) { } } -TEST(DateArrayAccessor, Test_Date64Array_CDataType_DATE) { +TEST(DateArrayAccessor, TestDate64ArrayCDataTypeDate) { std::vector values = { 86400000, 172800000, 259200000, 1649793238110, 0, 345600000, 432000000, 518400000, -86400000, -17987443200000, -24268068949000}; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/decimal_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/decimal_array_accessor_test.cc index b2eb9450c2f..6664b2d6e60 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/decimal_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/decimal_array_accessor_test.cc @@ -19,7 +19,8 @@ #include "arrow/builder.h" #include "arrow/testing/builder.h" #include "arrow/util/decimal.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { namespace { @@ -93,7 +94,7 @@ void AssertNumericOutput(int input_precision, int input_scale, } } -TEST(DecimalArrayFlightSqlAccessor, Test_Decimal128Array_CDataType_NUMERIC_SameScale) { +TEST(DecimalArrayFlightSqlAccessor, TestDecimal128ArrayCDataTypeNumericSameScale) { const std::vector& input_values = {"25.212", "-25.212", "-123456789.123", "123456789.123"}; const std::vector& output_values = diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/primitive_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/primitive_array_accessor_test.cc index 2f04b7324a5..a5ce05fb717 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/primitive_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/primitive_array_accessor_test.cc @@ -19,7 +19,8 @@ #include "arrow/flight/sql/odbc/odbc_impl/diagnostics.h" #include "arrow/testing/builder.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { @@ -52,43 +53,43 @@ void TestPrimitiveArraySqlAccessor() { } } -TEST(PrimitiveArrayFlightSqlAccessor, Test_Int64Array_CDataType_SBIGINT) { +TEST(PrimitiveArrayFlightSqlAccessor, TestInt64ArrayCDataTypeSbigint) { TestPrimitiveArraySqlAccessor(); } -TEST(PrimitiveArrayFlightSqlAccessor, Test_Int32Array_CDataType_SLONG) { +TEST(PrimitiveArrayFlightSqlAccessor, TestInt32ArrayCDataTypeSlong) { TestPrimitiveArraySqlAccessor(); } -TEST(PrimitiveArrayFlightSqlAccessor, Test_Int16Array_CDataType_SSHORT) { +TEST(PrimitiveArrayFlightSqlAccessor, TestInt16ArrayCDataTypeSshort) { TestPrimitiveArraySqlAccessor(); } -TEST(PrimitiveArrayFlightSqlAccessor, Test_Int8Array_CDataType_STINYINT) { +TEST(PrimitiveArrayFlightSqlAccessor, TestInt8ArrayCDataTypeStinyint) { TestPrimitiveArraySqlAccessor(); } -TEST(PrimitiveArrayFlightSqlAccessor, Test_UInt64Array_CDataType_UBIGINT) { +TEST(PrimitiveArrayFlightSqlAccessor, TestUInt64ArrayCDataTypeUbigint) { TestPrimitiveArraySqlAccessor(); } -TEST(PrimitiveArrayFlightSqlAccessor, Test_UInt32Array_CDataType_ULONG) { +TEST(PrimitiveArrayFlightSqlAccessor, TestUInt32ArrayCDataTypeUlong) { TestPrimitiveArraySqlAccessor(); } -TEST(PrimitiveArrayFlightSqlAccessor, Test_UInt16Array_CDataType_USHORT) { +TEST(PrimitiveArrayFlightSqlAccessor, TestUInt16ArrayCDataTypeUshort) { TestPrimitiveArraySqlAccessor(); } -TEST(PrimitiveArrayFlightSqlAccessor, Test_UInt8Array_CDataType_UTINYINT) { +TEST(PrimitiveArrayFlightSqlAccessor, TestUInt8ArrayCDataTypeUtinyint) { TestPrimitiveArraySqlAccessor(); } -TEST(PrimitiveArrayFlightSqlAccessor, Test_FloatArray_CDataType_FLOAT) { +TEST(PrimitiveArrayFlightSqlAccessor, TestFloatArrayCDataTypeFloat) { TestPrimitiveArraySqlAccessor(); } -TEST(PrimitiveArrayFlightSqlAccessor, Test_DoubleArray_CDataType_DOUBLE) { +TEST(PrimitiveArrayFlightSqlAccessor, TestDoubleArrayCDataTypeDouble) { TestPrimitiveArraySqlAccessor(); } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/string_array_accessor.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/string_array_accessor.cc index 69b3b304945..441b2a3394e 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/string_array_accessor.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/string_array_accessor.cc @@ -18,6 +18,7 @@ #include "arrow/flight/sql/odbc/odbc_impl/accessors/string_array_accessor.h" #include +#include #include "arrow/array.h" #include "arrow/flight/sql/odbc/odbc_impl/encoding.h" @@ -79,7 +80,7 @@ inline RowStatus MoveSingleCellToCharBuffer( auto* byte_buffer = static_cast(binding->buffer) + i * binding->buffer_length; auto* char_buffer = (CHAR_TYPE*)byte_buffer; - memcpy(char_buffer, ((char*)value) + value_offset, value_length); + std::memcpy(char_buffer, ((char*)value) + value_offset, value_length); // Write a NUL terminator if (binding->buffer_length >= remaining_length + sizeof(CHAR_TYPE)) { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/string_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/string_array_accessor_test.cc index eb7a9c88b3b..4d0e1393407 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/string_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/string_array_accessor_test.cc @@ -19,11 +19,12 @@ #include "arrow/flight/sql/odbc/odbc_impl/encoding.h" #include "arrow/testing/builder.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { -TEST(StringArrayAccessor, Test_CDataType_CHAR_Basic) { +TEST(StringArrayAccessor, TestCDataTypeCharBasic) { std::vector values = {"foo", "barx", "baz123"}; std::shared_ptr array; ArrayFromVector(values, &array); @@ -49,7 +50,7 @@ TEST(StringArrayAccessor, Test_CDataType_CHAR_Basic) { } } -TEST(StringArrayAccessor, Test_CDataType_CHAR_Truncation) { +TEST(StringArrayAccessor, TestCDataTypeCharTruncation) { std::vector values = {"ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF"}; std::shared_ptr array; ArrayFromVector(values, &array); @@ -82,7 +83,7 @@ TEST(StringArrayAccessor, Test_CDataType_CHAR_Truncation) { ASSERT_EQ(values[0], ss.str()); } -TEST(StringArrayAccessor, Test_CDataType_WCHAR_Basic) { +TEST(StringArrayAccessor, TestCDataTypeWcharBasic) { std::vector values = {"foo", "barx", "baz123"}; std::shared_ptr array; ArrayFromVector(values, &array); @@ -112,7 +113,7 @@ TEST(StringArrayAccessor, Test_CDataType_WCHAR_Basic) { } } -TEST(StringArrayAccessor, Test_CDataType_WCHAR_Truncation) { +TEST(StringArrayAccessor, TestCDataTypeWcharTruncation) { std::vector values = {"ABCDEFA"}; std::shared_ptr array; ArrayFromVector(values, &array); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/time_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/time_array_accessor_test.cc index eb49e4078cd..41bd0d73ea7 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/time_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/time_array_accessor_test.cc @@ -20,11 +20,12 @@ #include "arrow/flight/sql/odbc/odbc_impl/calendar_utils.h" #include "arrow/flight/sql/odbc/odbc_impl/util.h" #include "arrow/testing/builder.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { -TEST(TEST_TIME32, TIME_WITH_SECONDS) { +TEST(TestTime32, TimeWithSeconds) { auto value_field = field("f0", time32(TimeUnit::SECOND)); std::vector t32_values = {14896, 14897, 14892, 85400, 14893, 14895}; @@ -58,7 +59,7 @@ TEST(TEST_TIME32, TIME_WITH_SECONDS) { } } -TEST(TEST_TIME32, TIME_WITH_MILLI) { +TEST(TestTime32, TimeWithMilli) { auto value_field = field("f0", time32(TimeUnit::MILLI)); std::vector t32_values = {14896000, 14897000, 14892000, 85400000, 14893000, 14895000}; @@ -94,7 +95,7 @@ TEST(TEST_TIME32, TIME_WITH_MILLI) { } } -TEST(TEST_TIME64, TIME_WITH_MICRO) { +TEST(TestTime32, TimeWithMicro) { auto value_field = field("f0", time64(TimeUnit::MICRO)); std::vector t64_values = {14896000, 14897000, 14892000, @@ -131,7 +132,7 @@ TEST(TEST_TIME64, TIME_WITH_MICRO) { } } -TEST(TEST_TIME64, TIME_WITH_NANO) { +TEST(TestTime32, TimeWithNano) { auto value_field = field("f0", time64(TimeUnit::NANO)); std::vector t64_values = {14896000000, 14897000000, 14892000000, 85400000000, 14893000000, 14895000000}; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/timestamp_array_accessor.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/timestamp_array_accessor.cc index 37f14ebd9c5..93af9465576 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/timestamp_array_accessor.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/timestamp_array_accessor.cc @@ -19,12 +19,12 @@ #include "arrow/flight/sql/odbc/odbc_impl/calendar_utils.h" -using arrow::TimeUnit; +#include +#include namespace arrow::flight::sql::odbc { namespace { - -int64_t GetConversionToSecondsDivisor(TimeUnit::type unit) { +inline int64_t GetConversionToSecondsDivisor(TimeUnit::type unit) { int64_t divisor = 1; switch (unit) { case TimeUnit::SECOND: @@ -79,6 +79,10 @@ template RowStatus TimestampArrayFlightSqlAccessor::MoveSingleCellImpl( ColumnBinding* binding, int64_t arrow_row, int64_t cell_counter, int64_t& value_offset, bool update_value_offset, 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); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/timestamp_array_accessor_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/timestamp_array_accessor_test.cc index 393dd98501d..dd4917b0e37 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/timestamp_array_accessor_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/timestamp_array_accessor_test.cc @@ -20,11 +20,12 @@ #include "arrow/flight/sql/odbc/odbc_impl/calendar_utils.h" #include "arrow/flight/sql/odbc/odbc_impl/util.h" #include "arrow/testing/builder.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { -TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MILLI) { +TEST(TestTimestamp, TimestampWithMilli) { std::vector values = {86400370, 172800000, 259200000, 1649793238110LL, 345600000, 432000000, 518400000, -86399000, 0, @@ -88,7 +89,7 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MILLI) { } } -TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_SECONDS) { +TEST(TestTimestamp, TimestampWithSeconds) { std::vector values = {86400, 172800, 259200, 1649793238, 345600, 432000, 518400}; @@ -130,7 +131,7 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_SECONDS) { } } -TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MICRO) { +TEST(TestTimestamp, TimestampWithMicro) { std::vector values = {86400000000, 1649793238000000}; std::shared_ptr timestamp_array; @@ -174,7 +175,7 @@ TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_MICRO) { } } -TEST(TEST_TIMESTAMP, TIMESTAMP_WITH_NANO) { +TEST(TestTimestamp, TimestampWithNano) { std::vector values = {86400000010000, 1649793238000000000}; std::shared_ptr timestamp_array; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/types.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/types.h index ca33d872fa7..c0084a5ab15 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/types.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/accessors/types.h @@ -102,7 +102,7 @@ class FlightSqlAccessor : public Accessor { throw NullWithoutIndicatorException(); } } else { - // TODO: Optimize this by creating different versions of MoveSingleCell + // GH-47849 TODO: Optimize this by creating different versions of MoveSingleCell // depending on if str_len_buffer is null. auto row_status = MoveSingleCell(binding, current_arrow_row, i, value_offset, update_value_offset, diagnostics); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/address_info.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/address_info.cc index 5ee6674c3c2..5dfc85a58f7 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/address_info.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/address_info.cc @@ -16,8 +16,9 @@ // under the License. #include "arrow/flight/sql/odbc/odbc_impl/address_info.h" +#include -namespace driver { +namespace arrow::flight::sql::odbc { bool AddressInfo::GetAddressInfo(const std::string& host, char* host_name_info, int64_t max_host) { @@ -34,7 +35,7 @@ bool AddressInfo::GetAddressInfo(const std::string& host, char* host_name_info, } error = getnameinfo(addrinfo_result_->ai_addr, addrinfo_result_->ai_addrlen, - host_name_info, static_cast(max_host), NULL, 0, 0); + host_name_info, static_cast(max_host), NULL, 0, 0); return error == 0; } @@ -46,4 +47,5 @@ AddressInfo::~AddressInfo() { } AddressInfo::AddressInfo() : addrinfo_result_(nullptr) {} -} // namespace driver + +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/address_info.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/address_info.h index c127c0f4a29..7d538f912e0 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/address_info.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/address_info.h @@ -27,7 +27,7 @@ # include #endif -namespace driver { +namespace arrow::flight::sql::odbc { class AddressInfo { private: @@ -40,4 +40,5 @@ class AddressInfo { bool GetAddressInfo(const std::string& host, char* host_name_info, int64_t max_host); }; -} // namespace driver + +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h index 315d854b60d..cfda4cf29a7 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h @@ -29,6 +29,8 @@ // GH-48083 TODO: replace `namespace ODBC` with `namespace arrow::flight::sql::odbc` namespace ODBC { +using arrow::flight::sql::odbc::Diagnostics; +using arrow::flight::sql::odbc::WcsToUtf8; template inline void GetAttribute(T attribute_value, SQLPOINTER output, O output_size, @@ -66,7 +68,7 @@ inline SQLRETURN GetAttributeUTF8(std::string_view attribute_value, SQLPOINTER o template inline SQLRETURN GetAttributeUTF8(std::string_view attribute_value, SQLPOINTER output, O output_size, O* output_len_ptr, - arrow::flight::sql::odbc::Diagnostics& diagnostics) { + Diagnostics& diagnostics) { SQLRETURN result = GetAttributeUTF8(attribute_value, output, output_size, output_len_ptr); if (SQL_SUCCESS_WITH_INFO == result) { @@ -103,10 +105,10 @@ inline SQLRETURN GetAttributeSQLWCHAR(std::string_view attribute_value, } template -inline SQLRETURN GetAttributeSQLWCHAR( - const std::string& attribute_value, bool is_length_in_bytes, SQLPOINTER output, - O output_size, O* output_len_ptr, - arrow::flight::sql::odbc::Diagnostics& diagnostics) { +inline SQLRETURN GetAttributeSQLWCHAR(const std::string& attribute_value, + bool is_length_in_bytes, SQLPOINTER output, + O output_size, O* output_len_ptr, + Diagnostics& diagnostics) { SQLRETURN result = GetAttributeSQLWCHAR(attribute_value, is_length_in_bytes, output, output_size, output_len_ptr); if (SQL_SUCCESS_WITH_INFO == result) { @@ -119,7 +121,7 @@ template inline SQLRETURN GetStringAttribute(bool is_unicode, std::string_view attribute_value, bool is_length_in_bytes, SQLPOINTER output, O output_size, O* output_len_ptr, - arrow::flight::sql::odbc::Diagnostics& diagnostics) { + Diagnostics& diagnostics) { SQLRETURN result = SQL_SUCCESS; if (is_unicode) { result = GetAttributeSQLWCHAR(attribute_value, is_length_in_bytes, output, @@ -157,11 +159,11 @@ inline void SetAttributeSQLWCHAR(SQLPOINTER new_value, SQLINTEGER input_length_i std::string& attribute_to_write) { thread_local std::vector utf8_str; if (input_length_in_bytes == SQL_NTS) { - arrow::flight::sql::odbc::WcsToUtf8(new_value, &utf8_str); + WcsToUtf8(new_value, &utf8_str); } else { - arrow::flight::sql::odbc::WcsToUtf8( - new_value, input_length_in_bytes / arrow::flight::sql::odbc::GetSqlWCharSize(), - &utf8_str); + WcsToUtf8(new_value, + input_length_in_bytes / arrow::flight::sql::odbc::GetSqlWCharSize(), + &utf8_str); } attribute_to_write.assign((char*)utf8_str.data()); } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/calendar_utils.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/calendar_utils.cc index 1dddae2a7c7..b47b33f2d93 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/calendar_utils.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/calendar_utils.cc @@ -40,6 +40,9 @@ int64_t GetTodayTimeFromEpoch() { #endif } +// GH-47631: add support for non-UTC time zone data. +// Read the time zone value from Arrow::Timestamp, and use the time zone value to convert +// seconds_since_epoch instead of converting to UTC time zone by default void GetTimeForSecondsSinceEpoch(const int64_t seconds_since_epoch, std::tm& out_tm) { std::memset(&out_tm, 0, sizeof(std::tm)); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc index df61f1247c7..75498710d23 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc @@ -16,6 +16,7 @@ // under the License. #include "arrow/flight/sql/odbc/odbc_impl/config/configuration.h" + #include "arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.h" #include "arrow/flight/sql/odbc/odbc_impl/util.h" #include "arrow/result.h" @@ -186,15 +187,14 @@ const Connection::ConnPropertyMap& Configuration::GetProperties() const { return this->properties_; } -std::vector Configuration::GetCustomKeys() const { +std::vector Configuration::GetCustomKeys() const { Connection::ConnPropertyMap copy_props(properties_); for (auto& key : FlightSqlConnection::ALL_KEYS) { copy_props.erase(std::string(key)); } - std::vector keys; + std::vector keys; boost::copy(copy_props | boost::adaptors::map_keys, std::back_inserter(keys)); return keys; } - } // namespace config } // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h index 0390a57e52f..9a3350a316e 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h @@ -22,8 +22,10 @@ #include "arrow/flight/sql/odbc/odbc_impl/platform.h" #include "arrow/flight/sql/odbc/odbc_impl/spi/connection.h" +#if defined _WIN32 || defined _WIN64 // winuser.h needs to be included after windows.h, which is defined in platform.h -#include +# include +#endif namespace arrow::flight::sql::odbc { namespace config { @@ -60,7 +62,7 @@ class Configuration { */ const Connection::ConnPropertyMap& GetProperties() const; - std::vector GetCustomKeys() const; + std::vector GetCustomKeys() const; private: Connection::ConnPropertyMap properties_; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/encoding_utils.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/encoding_utils.h index 7f8a4a7ef85..5e3a4ecbdae 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/encoding_utils.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/encoding_utils.h @@ -31,13 +31,17 @@ #define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING namespace ODBC { +using arrow::flight::sql::odbc::DriverException; +using arrow::flight::sql::odbc::GetSqlWCharSize; +using arrow::flight::sql::odbc::Utf8ToWcs; +using arrow::flight::sql::odbc::WcsToUtf8; // Return the number of bytes required for the conversion. template inline size_t ConvertToSqlWChar(std::string_view str, SQLWCHAR* buffer, SQLLEN buffer_size_in_bytes) { thread_local std::vector wstr; - arrow::flight::sql::odbc::Utf8ToWcs(str.data(), str.size(), &wstr); + Utf8ToWcs(str.data(), str.size(), &wstr); SQLLEN value_length_in_bytes = wstr.size(); if (buffer) { @@ -46,14 +50,11 @@ inline size_t ConvertToSqlWChar(std::string_view str, SQLWCHAR* buffer, // Write a NUL terminator if (buffer_size_in_bytes >= - value_length_in_bytes + - static_cast(arrow::flight::sql::odbc::GetSqlWCharSize())) { - reinterpret_cast( - buffer)[value_length_in_bytes / arrow::flight::sql::odbc::GetSqlWCharSize()] = + value_length_in_bytes + static_cast(GetSqlWCharSize())) { + reinterpret_cast(buffer)[value_length_in_bytes / GetSqlWCharSize()] = '\0'; } else { - SQLLEN num_chars_written = - buffer_size_in_bytes / arrow::flight::sql::odbc::GetSqlWCharSize(); + SQLLEN num_chars_written = buffer_size_in_bytes / GetSqlWCharSize(); // If we failed to even write one char, the buffer is too small to hold a // NUL-terminator. if (num_chars_written > 0) { @@ -66,16 +67,15 @@ inline size_t ConvertToSqlWChar(std::string_view str, SQLWCHAR* buffer, inline size_t ConvertToSqlWChar(std::string_view str, SQLWCHAR* buffer, SQLLEN buffer_size_in_bytes) { - switch (arrow::flight::sql::odbc::GetSqlWCharSize()) { + switch (GetSqlWCharSize()) { case sizeof(char16_t): return ConvertToSqlWChar(str, buffer, buffer_size_in_bytes); case sizeof(char32_t): return ConvertToSqlWChar(str, buffer, buffer_size_in_bytes); default: assert(false); - throw arrow::flight::sql::odbc::DriverException( - "Encoding is unsupported, SQLWCHAR size: " + - std::to_string(arrow::flight::sql::odbc::GetSqlWCharSize())); + throw DriverException("Encoding is unsupported, SQLWCHAR size: " + + std::to_string(GetSqlWCharSize())); } } @@ -91,9 +91,9 @@ inline std::string SqlWcharToString(SQLWCHAR* wchar_msg, SQLINTEGER msg_len = SQ thread_local std::vector utf8_str; if (msg_len == SQL_NTS) { - arrow::flight::sql::odbc::WcsToUtf8((void*)wchar_msg, &utf8_str); + WcsToUtf8((void*)wchar_msg, &utf8_str); } else { - arrow::flight::sql::odbc::WcsToUtf8((void*)wchar_msg, msg_len, &utf8_str); + WcsToUtf8((void*)wchar_msg, msg_len, &utf8_str); } return std::string(utf8_str.begin(), utf8_str.end()); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_auth_method.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_auth_method.cc index 587dbdfb96b..5da662e7ade 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_auth_method.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_auth_method.cc @@ -37,6 +37,9 @@ class NoOpAuthMethod : public FlightSqlAuthMethod { void Authenticate(FlightSqlConnection& connection, FlightCallOptions& call_options) override { // Do nothing + + // GH-46733 TODO: implement NoOpAuthMethod to validate server address. + // Can use NoOpClientAuthHandler. } }; @@ -66,10 +69,10 @@ class UserPasswordAuthMethod : public FlightSqlAuthMethod { FlightCallOptions auth_call_options; const std::optional& login_timeout = connection.GetAttribute(Connection::LOGIN_TIMEOUT); - if (login_timeout && boost::get(*login_timeout) > 0) { + if (login_timeout && std::get(*login_timeout) > 0) { // ODBC's LOGIN_TIMEOUT attribute and FlightCallOptions.timeout use // seconds as time unit. - double timeout_seconds = static_cast(boost::get(*login_timeout)); + double timeout_seconds = static_cast(std::get(*login_timeout)); if (timeout_seconds > 0) { auth_call_options.timeout = TimeoutDuration{timeout_seconds}; } @@ -94,7 +97,9 @@ class UserPasswordAuthMethod : public FlightSqlAuthMethod { throw 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_; } @@ -116,10 +121,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 Status status = client_.Authenticate( call_options, std::unique_ptr(new NoOpClientAuthHandler())); @@ -143,22 +149,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/odbc_impl/flight_sql_connection.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.cc index 422c45fc059..6c9f5a5ed19 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.cc @@ -119,7 +119,7 @@ const std::set BUILT_IN_PROPERTIES Connection::ConnPropertyMap::const_iterator TrackMissingRequiredProperty( 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); } @@ -138,6 +138,7 @@ std::shared_ptr LoadFlightSslConfigs( AsBool(conn_property_map, FlightSqlConnection::USE_SYSTEM_TRUST_STORE) .value_or(SYSTEM_TRUST_STORE_DEFAULT); + // GH-47630: find co-located TLS certificate if `trusted certs` path is not specified auto trusted_certs_iterator = conn_property_map.find(std::string(FlightSqlConnection::TRUSTED_CERTS)); auto trusted_certs = trusted_certs_iterator != conn_property_map.end() @@ -246,9 +247,9 @@ const FlightCallOptions& FlightSqlConnection::PopulateCallOptions( // is the first request. const std::optional& connection_timeout = closed_ ? GetAttribute(LOGIN_TIMEOUT) : GetAttribute(CONNECTION_TIMEOUT); - if (connection_timeout && boost::get(*connection_timeout) > 0) { + if (connection_timeout && std::get(*connection_timeout) > 0) { call_options_.timeout = - TimeoutDuration{static_cast(boost::get(*connection_timeout))}; + TimeoutDuration{static_cast(std::get(*connection_timeout))}; } for (auto prop : props) { @@ -323,7 +324,7 @@ Location FlightSqlConnection::BuildLocation( Location location; if (ssl_config->UseEncryption()) { - driver::AddressInfo address_info; + AddressInfo address_info; char host_name_info[NI_MAXHOST] = ""; bool operation_result = false; @@ -337,7 +338,7 @@ Location FlightSqlConnection::BuildLocation( ThrowIfNotOK(Location::ForGrpcTls(host_name_info, port).Value(&location)); return location; } - // TODO: We should log that we could not convert an IP to hostname here. + // GH-47852 TODO: We should log that we could not convert an IP to hostname here. } } catch (...) { // This is expected. The Host attribute can be an IP or name, but make_address will @@ -405,7 +406,7 @@ Connection::Info FlightSqlConnection::GetInfo(uint16_t info_type) { if (info_type == SQL_DBMS_NAME || info_type == SQL_SERVER_NAME) { // Update the database component reported in error messages. // We do this lazily for performance reasons. - diagnostics_.SetDataSourceComponent(boost::get(result)); + diagnostics_.SetDataSourceComponent(std::get(result)); } return result; } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.h index 2561ea492f0..d1a194abcbd 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.h @@ -34,7 +34,7 @@ class FlightSqlSslConfig; /// \brief Create an instance of the FlightSqlSslConfig class, from the properties passed /// into the map. /// \param conn_property_map the map with the Connection properties. -/// \return An instance of the FlightSqlSslConfig. +/// \return An instance of the FlightSqlSslConfig. std::shared_ptr LoadFlightSslConfigs( const Connection::ConnPropertyMap& conn_property_map); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection_test.cc index 87ae526f158..bc26a113d77 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection_test.cc @@ -19,7 +19,8 @@ #include "arrow/flight/sql/odbc/odbc_impl/platform.h" #include "arrow/flight/types.h" -#include "gtest/gtest.h" + +#include #include @@ -33,17 +34,16 @@ TEST(AttributeTests, SetAndGetAttribute) { const std::optional first_value = connection.GetAttribute(Connection::CONNECTION_TIMEOUT); - EXPECT_TRUE(first_value); - - EXPECT_EQ(static_cast(200), boost::get(*first_value)); + ASSERT_TRUE(first_value); + ASSERT_EQ(static_cast(200), std::get(*first_value)); connection.SetAttribute(Connection::CONNECTION_TIMEOUT, static_cast(300)); const std::optional change_value = connection.GetAttribute(Connection::CONNECTION_TIMEOUT); - EXPECT_TRUE(change_value); - EXPECT_EQ(static_cast(300), boost::get(*change_value)); + ASSERT_TRUE(change_value); + ASSERT_EQ(static_cast(300), std::get(*change_value)); connection.Close(); } @@ -55,7 +55,7 @@ TEST(AttributeTests, GetAttributeWithoutSetting) { connection.GetAttribute(Connection::CONNECTION_TIMEOUT); connection.SetClosed(false); - EXPECT_EQ(0, boost::get(*optional)); + EXPECT_EQ(0, std::get(*optional)); connection.Close(); } @@ -77,8 +77,8 @@ TEST(MetadataSettingsTest, StringColumnLengthTest) { const std::optional actual_string_column_length = connection.GetStringColumnLength(properties); - EXPECT_TRUE(actual_string_column_length); - EXPECT_EQ(expected_string_column_length, *actual_string_column_length); + ASSERT_TRUE(actual_string_column_length); + ASSERT_EQ(expected_string_column_length, *actual_string_column_length); connection.Close(); } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_get_tables_reader.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_get_tables_reader.cc index ebff8c40f2c..a668353272b 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_get_tables_reader.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_get_tables_reader.cc @@ -77,9 +77,9 @@ 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. + // GH-46561 TODO: Test and build the driver against a server that returns + // complex types columns with the children + // types and handle the failure properly return nullptr; } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_column.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_column.h index ede53038f1a..d09e550900b 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_column.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_column.h @@ -70,4 +70,5 @@ class FlightSqlResultSetColumn { } } }; + } // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.cc index 8ac3c7ed752..52bfcfa4901 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.cc @@ -20,6 +20,7 @@ #include "arrow/flight/sql/column_metadata.h" #include "arrow/flight/sql/odbc/odbc_impl/platform.h" #include "arrow/flight/sql/odbc/odbc_impl/util.h" +#include "arrow/util/key_value_metadata.h" #include #include "arrow/flight/sql/odbc/odbc_impl/exceptions.h" @@ -40,12 +41,8 @@ constexpr int32_t DefaultDecimalPrecision = 38; constexpr int32_t DefaultLengthForVariableLengthColumns = 1024; namespace { -std::shared_ptr empty_metadata_map(new KeyValueMetadata); - inline ColumnMetadata GetMetadata(const std::shared_ptr& field) { - const auto& metadata_map = field->metadata(); - - ColumnMetadata metadata(metadata_map ? metadata_map : empty_metadata_map); + ColumnMetadata metadata(field->metadata()); return metadata; } @@ -160,19 +157,27 @@ size_t FlightSqlResultSetMetadata::GetLength(int column_position) { } std::string FlightSqlResultSetMetadata::GetLiteralPrefix(int column_position) { - // TODO: Flight SQL column metadata does not have this, should we add to the spec? + // GH-47853 TODO: use `ColumnMetadata` to get literal prefix after Flight SQL protocol + // adds support for it + + // Flight SQL column metadata does not have literal prefix, empty string is returned return ""; } std::string FlightSqlResultSetMetadata::GetLiteralSuffix(int column_position) { - // TODO: Flight SQL column metadata does not have this, should we add to the spec? + // GH-47853 TODO: use `ColumnMetadata` to get literal suffix after Flight SQL protocol + // adds support for it + + // Flight SQL column metadata does not have literal suffix, empty string is returned return ""; } std::string FlightSqlResultSetMetadata::GetLocalTypeName(int column_position) { ColumnMetadata metadata = GetMetadata(schema_->field(column_position - 1)); - // TODO: Is local type name the same as type name? + // Local type name is for display purpose only. + // Return type name as local type name as Flight SQL protocol doesn't have support for + // local type name. return metadata.GetTypeName().ValueOrElse([] { return ""; }); } @@ -195,7 +200,7 @@ size_t FlightSqlResultSetMetadata::GetOctetLength(int column_position) { // Workaround to get the precision for Decimal and Numeric types, since server doesn't // return it currently. - // TODO: Use the server precision when its fixed. + // GH-47854 TODO: Use the server precision when its fixed. std::shared_ptr arrow_type = field->type(); if (arrow_type->id() == Type::DECIMAL128) { int32_t precision = util::GetDecimalTypePrecision(arrow_type); @@ -207,10 +212,13 @@ size_t FlightSqlResultSetMetadata::GetOctetLength(int column_position) { .value_or(DefaultLengthForVariableLengthColumns); } -std::string FlightSqlResultSetMetadata::GetTypeName(int column_position) { +std::string FlightSqlResultSetMetadata::GetTypeName(int column_position, int data_type) { ColumnMetadata metadata = GetMetadata(schema_->field(column_position - 1)); - return metadata.GetTypeName().ValueOrElse([] { return ""; }); + return metadata.GetTypeName().ValueOrElse([data_type] { + // If we get an empty type name, figure out the type name from the data_type. + return util::GetTypeNameFromSqlDataType(data_type); + }); } Updatability FlightSqlResultSetMetadata::GetUpdatable(int column_position) { @@ -220,7 +228,6 @@ Updatability FlightSqlResultSetMetadata::GetUpdatable(int column_position) { bool FlightSqlResultSetMetadata::IsAutoUnique(int column_position) { ColumnMetadata metadata = GetMetadata(schema_->field(column_position - 1)); - // TODO: Is AutoUnique equivalent to AutoIncrement? return metadata.GetIsAutoIncrement().ValueOrElse([] { return false; }); } @@ -241,18 +248,29 @@ bool FlightSqlResultSetMetadata::IsUnsigned(int column_position) { const std::shared_ptr& field = schema_->field(column_position - 1); switch (field->type()->id()) { + case Type::INT8: + case Type::INT16: + case Type::INT32: + case Type::INT64: + case Type::DOUBLE: + case Type::FLOAT: + case Type::HALF_FLOAT: + case Type::DECIMAL32: + case Type::DECIMAL64: + case Type::DECIMAL128: + case Type::DECIMAL256: + return false; case Type::UINT8: case Type::UINT16: case Type::UINT32: case 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/odbc_impl/flight_sql_result_set_metadata.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.h index 0d141a4bb9c..11b1678c24d 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_result_set_metadata.h @@ -77,7 +77,7 @@ class FlightSqlResultSetMetadata : public ResultSetMetadata { size_t GetOctetLength(int column_position) override; - std::string GetTypeName(int column_position) override; + std::string GetTypeName(int column_position, int data_type) override; Updatability GetUpdatable(int column_position) override; @@ -87,6 +87,7 @@ class FlightSqlResultSetMetadata : public ResultSetMetadata { Searchability IsSearchable(int column_position) override; + /// \brief Returns true if the column is unsigned (not numeric) bool IsUnsigned(int column_position) override; bool IsFixedPrecScale(int column_position) override; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_ssl_config.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_ssl_config.h index 10b12149712..c2d1423e97e 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_ssl_config.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_ssl_config.h @@ -17,9 +17,9 @@ #pragma once -#include -#include #include +#include "arrow/flight/types.h" +#include "arrow/status.h" namespace arrow::flight::sql::odbc { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement.cc index c35154b44ea..ef652d34fac 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement.cc @@ -83,9 +83,9 @@ bool FlightSqlStatement::SetAttribute(StatementAttributeId attribute, case MAX_LENGTH: return CheckIfSetToOnlyValidValue(value, static_cast(0)); case QUERY_TIMEOUT: - if (boost::get(value) > 0) { + if (std::get(value) > 0) { call_options_.timeout = - TimeoutDuration{static_cast(boost::get(value))}; + TimeoutDuration{static_cast(std::get(value))}; } else { call_options_.timeout = TimeoutDuration{-1}; // Intentional fall-through. diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_columns.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_columns.cc index 914cd8fa452..a6200c0b1c1 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_columns.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_columns.cc @@ -26,6 +26,8 @@ namespace arrow::flight::sql::odbc { using arrow::Result; +using arrow::Schema; +using arrow::flight::sql::ColumnMetadata; using util::AppendToBuilder; using std::make_optional; @@ -99,10 +101,9 @@ Result> TransformInner( 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. + // GH-46561 TODO: Test and build the driver against a server that returns + // complex types columns with the children + // types and handle the failure properly. continue; } for (int i = 0; i < schema->num_fields(); ++i) { @@ -126,8 +127,8 @@ Result> TransformInner( ? data_type_v3 : util::ConvertSqlDataTypeFromV3ToV2(data_type_v3); - // TODO: Use `metadata.GetTypeName()` when ARROW-16064 is merged. - const auto& type_name_result = field->metadata()->Get("ARROW:FLIGHT:SQL:TYPE_NAME"); + const auto& type_name_result = metadata.GetTypeName(); + data.type_name = type_name_result.ok() ? type_name_result.ValueOrDie() : util::GetTypeNameFromSqlDataType(data_type_v3); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_tables.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_tables.h index 0c3ad10f97b..5687134f1eb 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_tables.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_tables.h @@ -61,5 +61,4 @@ std::shared_ptr GetTablesForGenericUse( const std::string* catalog_name, const std::string* schema_name, const std::string* table_name, const std::vector& table_types, Diagnostics& diagnostics, const MetadataSettings& metadata_settings); - } // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_type_info.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_type_info.cc index e94378b7e04..95c0753603b 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_type_info.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_type_info.cc @@ -108,7 +108,7 @@ Result> TransformInner( data.literal_suffix = reader.GetLiteralSuffix(); const auto& create_params = reader.GetCreateParams(); - if (create_params) { + if (create_params && !create_params->empty()) { data.create_params = boost::algorithm::join(*create_params, ","); } else { data.create_params = nullopt; @@ -116,6 +116,8 @@ Result> TransformInner( data.nullable = reader.GetNullable() ? NULLABILITY_NULLABLE : NULLABILITY_NO_NULLS; data.case_sensitive = reader.GetCaseSensitive(); + // GH-47237 return SEARCHABILITY_LIKE_ONLY and SEARCHABILITY_ALL_EXPECT_LIKE for + // appropriate data types data.searchable = reader.GetSearchable() ? SEARCHABILITY_ALL : SEARCHABILITY_NONE; data.unsigned_attribute = reader.GetUnsignedAttribute(); data.fixed_prec_scale = reader.GetFixedPrecScale(); @@ -123,9 +125,9 @@ Result> TransformInner( data.local_type_name = reader.GetLocalTypeName(); data.minimum_scale = reader.GetMinimumScale(); data.maximum_scale = reader.GetMaximumScale(); - data.sql_data_type = + data.sql_data_type = util::GetNonConciseDataType( EnsureRightSqlCharType(static_cast(reader.GetSqlDataType()), - metadata_settings_.use_wide_char); + metadata_settings_.use_wide_char)); data.sql_datetime_sub = util::GetSqlDateTimeSubCode(static_cast(data.data_type)); data.num_prec_radix = reader.GetNumPrecRadix(); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_stream_chunk_buffer.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_stream_chunk_buffer.cc index be788d9d08a..d4812e30eee 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_stream_chunk_buffer.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_stream_chunk_buffer.cc @@ -20,8 +20,6 @@ namespace arrow::flight::sql::odbc { -using arrow::Result; - FlightStreamChunkBuffer::FlightStreamChunkBuffer( FlightSqlClient& flight_sql_client, const FlightClientOptions& client_options, const FlightCallOptions& call_options, const std::shared_ptr& flight_info, @@ -58,10 +56,10 @@ FlightStreamChunkBuffer::FlightStreamChunkBuffer( util::ThrowIfNotOK(result.status()); std::shared_ptr stream_reader_ptr(std::move(result.ValueOrDie())); - BlockingQueue, - std::shared_ptr>>::Supplier supplier = [=]() - -> std::optional< - std::pair, std::shared_ptr>> { + BlockingQueue, + std::shared_ptr>>::Supplier supplier = + [=]() -> std::optional, + std::shared_ptr>> { auto result = stream_reader_ptr->Next(); bool is_not_ok = !result.ok(); bool is_not_empty = result.ok() && (result.ValueOrDie().data != nullptr); @@ -82,13 +80,13 @@ FlightStreamChunkBuffer::FlightStreamChunkBuffer( } bool FlightStreamChunkBuffer::GetNext(FlightStreamChunk* chunk) { - std::pair, std::shared_ptr> + std::pair, std::shared_ptr> closeable_endpoint_stream_pair; if (!queue_.Pop(&closeable_endpoint_stream_pair)) { return false; } - Result result = closeable_endpoint_stream_pair.first; + arrow::Result result = closeable_endpoint_stream_pair.first; if (!result.status().ok()) { Close(); throw DriverException(result.status().message()); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/get_info_cache.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/get_info_cache.h index a1452e4b466..693ee000de5 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/get_info_cache.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/get_info_cache.h @@ -17,13 +17,12 @@ #pragma once -#include "arrow/flight/sql/client.h" -#include "arrow/flight/sql/odbc/odbc_impl/spi/connection.h" - #include #include #include #include +#include "arrow/flight/sql/client.h" +#include "arrow/flight/sql/odbc/odbc_impl/spi/connection.h" namespace arrow::flight::sql::odbc { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.cc index db6170f3102..acd68f0eef3 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.cc @@ -221,7 +221,7 @@ class ScalarToJson : public ScalarVisitor { } Status Visit(const DurationScalar& scalar) override { - // TODO: Append TimeUnit on conversion + // GH-47857 TODO: Append TimeUnit on conversion return ConvertScalarToStringAndWrite(scalar, writer_); } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.h index 9c0b42748a8..44321398b6f 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter.h @@ -17,8 +17,8 @@ #pragma once -#include #include +#include "arrow/type_fwd.h" namespace arrow::flight::sql::odbc { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter_test.cc index a3c3275affd..d6d7a3ed506 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/json_converter_test.cc @@ -19,7 +19,8 @@ #include "arrow/scalar.h" #include "arrow/testing/builder.h" #include "arrow/type.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { @@ -163,7 +164,7 @@ TEST(ConvertToJson, MonthInterval) { } TEST(ConvertToJson, Duration) { - // TODO: Append TimeUnit on conversion + // GH-47857 TODO: Append TimeUnit on conversion ASSERT_EQ("\"123\"", ConvertToJson(DurationScalar(123, TimeUnit::SECOND))); ASSERT_EQ("\"123\"", ConvertToJson(DurationScalar(123, TimeUnit::MILLI))); ASSERT_EQ("\"123\"", ConvertToJson(DurationScalar(123, TimeUnit::MICRO))); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.cc index 59cdc2e12d9..a2464aa00e3 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.cc @@ -21,6 +21,7 @@ #include "arrow/util/utf8.h" #include "arrow/flight/sql/odbc/odbc_impl/attribute_utils.h" +#include "arrow/flight/sql/odbc/odbc_impl/config/configuration.h" #include "arrow/flight/sql/odbc/odbc_impl/exceptions.h" #include "arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.h" #include "arrow/flight/sql/odbc/odbc_impl/odbc_environment.h" @@ -233,8 +234,8 @@ SQLRETURN ODBCConnection::GetInfo(SQLUSMALLINT info_type, SQLPOINTER value, case SQL_COLUMN_ALIAS: case SQL_DBMS_NAME: case SQL_DBMS_VER: - case SQL_DRIVER_NAME: // TODO: This should be the driver's filename and shouldn't - // come from the SPI. + case SQL_DRIVER_NAME: // GH-47858 TODO: This should be the driver's filename and + // shouldn't come from the SPI. case SQL_DRIVER_VER: case SQL_SEARCH_PATTERN_ESCAPE: case SQL_SERVER_NAME: @@ -260,7 +261,7 @@ SQLRETURN ODBCConnection::GetInfo(SQLUSMALLINT info_type, SQLPOINTER value, case SQL_SPECIAL_CHARACTERS: case SQL_XOPEN_CLI_YEAR: { const auto& info = spi_connection_->GetInfo(info_type); - const std::string& info_value = boost::get(info); + const std::string& info_value = std::get(info); return GetStringAttribute(is_unicode, info_value, true, value, buffer_length, output_length, GetDiagnostics()); } @@ -350,7 +351,7 @@ SQLRETURN ODBCConnection::GetInfo(SQLUSMALLINT info_type, SQLPOINTER value, case SQL_SQL92_VALUE_EXPRESSIONS: case SQL_STANDARD_CLI_CONFORMANCE: { const auto& info = spi_connection_->GetInfo(info_type); - uint32_t info_value = boost::get(info); + uint32_t info_value = std::get(info); GetAttribute(info_value, value, buffer_length, output_length); return SQL_SUCCESS; } @@ -385,7 +386,7 @@ SQLRETURN ODBCConnection::GetInfo(SQLUSMALLINT info_type, SQLPOINTER value, case SQL_ODBC_SQL_CONFORMANCE: case SQL_ODBC_SAG_CLI_CONFORMANCE: { const auto& info = spi_connection_->GetInfo(info_type); - uint16_t info_value = boost::get(info); + uint16_t info_value = std::get(info); GetAttribute(info_value, value, buffer_length, output_length); return SQL_SUCCESS; } @@ -396,7 +397,7 @@ SQLRETURN ODBCConnection::GetInfo(SQLUSMALLINT info_type, SQLPOINTER value, if (!attr) { throw DriverException("Optional feature not supported.", "HYC00"); } - const std::string& info_value = boost::get(*attr); + const std::string& info_value = std::get(*attr); return GetStringAttribute(is_unicode, info_value, true, value, buffer_length, output_length, GetDiagnostics()); } @@ -414,7 +415,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 @@ -422,7 +423,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 @@ -450,7 +451,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"); @@ -591,7 +592,7 @@ SQLRETURN ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, if (!catalog) { throw DriverException("Optional feature not supported.", "HYC00"); } - const std::string& info_value = boost::get(*catalog); + const std::string& info_value = std::get(*catalog); return GetStringAttribute(is_unicode, info_value, true, value, buffer_length, output_length, GetDiagnostics()); } @@ -620,7 +621,7 @@ SQLRETURN ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, throw DriverException("Invalid attribute", "HY092"); } - GetAttribute(static_cast(boost::get(*spi_attribute)), value, + GetAttribute(static_cast(std::get(*spi_attribute)), value, buffer_length, output_length); return SQL_SUCCESS; } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.h index a968e04edd5..5b80473c55e 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.h @@ -17,8 +17,8 @@ #pragma once -#include #include "arrow/flight/sql/odbc/odbc_impl/odbc_handle.h" +#include "arrow/flight/sql/odbc/odbc_impl/spi/connection.h" #include #include @@ -42,6 +42,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] spi_connection the underlying spi connection. ODBCConnection(ODBCEnvironment& environment, std::shared_ptr spi_connection); @@ -49,6 +52,11 @@ 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 arrow::flight::sql::odbc::Connection::ConnPropertyMap& properties, std::vector& missing_properties); @@ -56,7 +64,7 @@ class ODBCConnection : public ODBCHandle { SQLRETURN GetInfo(SQLUSMALLINT info_type, SQLPOINTER value, SQLSMALLINT buffer_length, SQLSMALLINT* output_length, bool is_unicode); void SetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER string_length, - bool isUnicode); + bool is_unicode); SQLRETURN GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER buffer_length, SQLINTEGER* output_length, bool is_unicode); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.cc index d2b7f8865ca..8c856fdbd6b 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.cc @@ -62,7 +62,7 @@ ODBCDescriptor::ODBCDescriptor(Diagnostics& base_diagnostics, ODBCConnection* co parent_statement_(stmt), array_status_ptr_(nullptr), bind_offset_ptr_(nullptr), - rows_processed_ptr_(nullptr), + rows_proccessed_ptr_(nullptr), array_size_(1), bind_type_(SQL_BIND_BY_COLUMN), highest_one_based_bound_record_(0), @@ -109,7 +109,7 @@ void ODBCDescriptor::SetHeaderField(SQLSMALLINT field_identifier, SQLPOINTER val has_bindings_changed_ = true; break; case SQL_DESC_ROWS_PROCESSED_PTR: - SetPointerAttribute(value, rows_processed_ptr_); + SetPointerAttribute(value, rows_proccessed_ptr_); has_bindings_changed_ = true; break; case SQL_DESC_COUNT: { @@ -273,10 +273,12 @@ void ODBCDescriptor::GetHeaderField(SQLSMALLINT field_identifier, SQLPOINTER val GetAttribute(bind_type_, value, buffer_length, output_length); break; case SQL_DESC_ROWS_PROCESSED_PTR: - GetAttribute(rows_processed_ptr_, value, buffer_length, output_length); + GetAttribute(rows_proccessed_ptr_, value, buffer_length, output_length); break; case SQL_DESC_COUNT: { - GetAttribute(highest_one_based_bound_record_, value, buffer_length, output_length); + // highest_one_based_bound_record_ equals number of records + 1 + GetAttribute(static_cast(highest_one_based_bound_record_ - 1), value, + buffer_length, output_length); break; } default: @@ -310,54 +312,55 @@ void ODBCDescriptor::GetField(SQLSMALLINT record_number, SQLSMALLINT field_ident throw DriverException("Invalid descriptor index", "07009"); } - // TODO: Restrict fields based on AppDescriptor IPD, and IRD. + // GH-47867 TODO: Restrict fields based on AppDescriptor IPD, and IRD. + bool length_in_bytes = true; SQLSMALLINT zero_based_record = record_number - 1; const DescriptorRecord& record = records_[zero_based_record]; switch (field_identifier) { case SQL_DESC_BASE_COLUMN_NAME: - GetAttributeUTF8(record.base_column_name, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.base_column_name, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_BASE_TABLE_NAME: - GetAttributeUTF8(record.base_table_name, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.base_table_name, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_CATALOG_NAME: - GetAttributeUTF8(record.catalog_name, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.catalog_name, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_LABEL: - GetAttributeUTF8(record.label, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.label, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_LITERAL_PREFIX: - GetAttributeUTF8(record.literal_prefix, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.literal_prefix, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_LITERAL_SUFFIX: - GetAttributeUTF8(record.literal_suffix, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.literal_suffix, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_LOCAL_TYPE_NAME: - GetAttributeUTF8(record.local_type_name, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.local_type_name, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_NAME: - GetAttributeUTF8(record.name, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.name, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_SCHEMA_NAME: - GetAttributeUTF8(record.schema_name, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.schema_name, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_TABLE_NAME: - GetAttributeUTF8(record.table_name, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.table_name, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_TYPE_NAME: - GetAttributeUTF8(record.type_name, value, buffer_length, output_length, - GetDiagnostics()); + GetAttributeSQLWCHAR(record.type_name, length_in_bytes, value, buffer_length, + output_length, GetDiagnostics()); break; case SQL_DESC_DATA_PTR: @@ -367,7 +370,7 @@ void ODBCDescriptor::GetField(SQLSMALLINT record_number, SQLSMALLINT field_ident case SQL_DESC_OCTET_LENGTH_PTR: GetAttribute(record.indicator_ptr, value, buffer_length, output_length); break; - + case SQL_COLUMN_LENGTH: // ODBC 2.0 case SQL_DESC_LENGTH: GetAttribute(record.length, value, buffer_length, output_length); break; @@ -407,12 +410,14 @@ void ODBCDescriptor::GetField(SQLSMALLINT record_number, SQLSMALLINT field_ident case SQL_DESC_PARAMETER_TYPE: GetAttribute(record.param_type, value, buffer_length, output_length); break; + case SQL_COLUMN_PRECISION: // ODBC 2.0 case SQL_DESC_PRECISION: GetAttribute(record.precision, value, buffer_length, output_length); break; case SQL_DESC_ROWVER: GetAttribute(record.row_ver, value, buffer_length, output_length); break; + case SQL_COLUMN_SCALE: // ODBC 2.0 case SQL_DESC_SCALE: GetAttribute(record.scale, value, buffer_length, output_length); break; @@ -479,6 +484,8 @@ void ODBCDescriptor::PopulateFromResultSetMetadata(ResultSetMetadata* rsmd) { for (size_t i = 0; i < records_.size(); ++i) { size_t one_based_index = i + 1; + int16_t concise_type = rsmd->GetConciseType(one_based_index); + records_[i].base_column_name = rsmd->GetBaseColumnName(one_based_index); records_[i].base_table_name = rsmd->GetBaseTableName(one_based_index); records_[i].catalog_name = rsmd->GetCatalogName(one_based_index); @@ -489,9 +496,8 @@ void ODBCDescriptor::PopulateFromResultSetMetadata(ResultSetMetadata* rsmd) { records_[i].name = rsmd->GetName(one_based_index); records_[i].schema_name = rsmd->GetSchemaName(one_based_index); records_[i].table_name = rsmd->GetTableName(one_based_index); - records_[i].type_name = rsmd->GetTypeName(one_based_index); - records_[i].concise_type = GetSqlTypeForODBCVersion( - rsmd->GetConciseType(one_based_index), is_2x_connection_); + records_[i].type_name = rsmd->GetTypeName(one_based_index, concise_type); + records_[i].concise_type = GetSqlTypeForODBCVersion(concise_type, is_2x_connection_); records_[i].data_ptr = nullptr; records_[i].indicator_ptr = nullptr; records_[i].display_size = rsmd->GetColumnDisplaySize(one_based_index); @@ -501,10 +507,12 @@ void ODBCDescriptor::PopulateFromResultSetMetadata(ResultSetMetadata* rsmd) { rsmd->IsAutoUnique(one_based_index) ? SQL_TRUE : SQL_FALSE; records_[i].case_sensitive = rsmd->IsCaseSensitive(one_based_index) ? SQL_TRUE : SQL_FALSE; - records_[i].datetime_interval_precision; // TODO - update when rsmd adds this + records_[i].datetime_interval_precision; // GH-47869 TODO implement + // `SQL_DESC_DATETIME_INTERVAL_PRECISION` SQLINTEGER num_prec_radix = rsmd->GetNumPrecRadix(one_based_index); records_[i].num_prec_radix = num_prec_radix > 0 ? num_prec_radix : 0; - records_[i].datetime_interval_code; // TODO + records_[i].datetime_interval_code; // GH-47868 TODO implement + // `SQL_DESC_DATETIME_INTERVAL_CODE` records_[i].fixed_prec_scale = rsmd->IsFixedPrecScale(one_based_index) ? SQL_TRUE : SQL_FALSE; records_[i].nullable = rsmd->IsNullable(one_based_index); @@ -573,5 +581,5 @@ void ODBCDescriptor::SetDataPtrOnRecord(SQLPOINTER data_ptr, SQLSMALLINT record_ } void DescriptorRecord::CheckConsistency() { - // TODO + // GH-47870 TODO implement } diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.h index d28adbc91d2..8a6cab82be0 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.h @@ -126,8 +126,8 @@ class ODBCDescriptor : public ODBCHandle { inline SQLUSMALLINT* GetArrayStatusPtr() { return array_status_ptr_; } inline void SetRowsProcessed(SQLULEN rows) { - if (rows_processed_ptr_) { - *rows_processed_ptr_ = rows; + if (rows_proccessed_ptr_) { + *rows_proccessed_ptr_ = rows; } } @@ -144,7 +144,7 @@ class ODBCDescriptor : public ODBCHandle { ODBCStatement* parent_statement_; SQLUSMALLINT* array_status_ptr_; SQLULEN* bind_offset_ptr_; - SQLULEN* rows_processed_ptr_; + SQLULEN* rows_proccessed_ptr_; SQLULEN array_size_; SQLINTEGER bind_type_; SQLSMALLINT highest_one_based_bound_record_; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_handle.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_handle.h index b3fd6e371a2..4674a4c9ff1 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_handle.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_handle.h @@ -17,13 +17,14 @@ #pragma once -#include -#include +// platform.h includes windows.h, so it needs to be included first +#include "arrow/flight/sql/odbc/odbc_impl/platform.h" #include #include #include #include +#include "arrow/flight/sql/odbc/odbc_impl/diagnostics.h" /** * @brief An abstraction over a generic ODBC handle. @@ -46,7 +47,18 @@ class ODBCHandle { try { GetDiagnostics().Clear(); rc = function(); - } catch (const arrow::flight::sql::odbc::DriverException& ex) { + } catch (const arrow::flight::sql::odbc::AuthenticationException& ex) { + GetDiagnostics().AddError(arrow::flight::sql::odbc::DriverException( + ex.GetMessageText(), ex.GetSqlState(), ex.GetNativeError())); + } catch (const arrow::flight::sql::odbc::NullWithoutIndicatorException& ex) { + GetDiagnostics().AddError(arrow::flight::sql::odbc::DriverException( + ex.GetMessageText(), ex.GetSqlState(), ex.GetNativeError())); + } + // on mac, DriverException doesn't catch the subclass exceptions hence we added + // the following above. + // GH-48278 TODO investigate if there is a way to catch all the subclass exceptions + // under DriverException + catch (const arrow::flight::sql::odbc::DriverException& ex) { GetDiagnostics().AddError(ex); } catch (const std::bad_alloc&) { GetDiagnostics().AddError(arrow::flight::sql::odbc::DriverException( diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc index a9fb91c00b4..db8c4982d1c 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc @@ -250,7 +250,7 @@ void ODBCStatement::CopyAttributesFromConnection(ODBCConnection& connection) { ODBCStatement& tracking_statement = connection.GetTrackingStatement(); // Get abstraction attributes and copy to this spi_statement_. - // Possible ODBC attributes are below, but many of these are not supported by warpdrive + // Possible ODBC attributes are below, but many of these are not supported by Arrow ODBC // or ODBCAbstaction: // SQL_ATTR_ASYNC_ENABLE: // SQL_ATTR_METADATA_ID: @@ -316,7 +316,8 @@ void ODBCStatement::ExecuteDirect(const std::string& query) { is_prepared_ = false; } -bool ODBCStatement::Fetch(size_t rows) { +bool ODBCStatement::Fetch(size_t rows, SQLULEN* row_count_ptr, + SQLUSMALLINT* row_status_array) { if (has_reached_end_of_result_) { ird_->SetRowsProcessed(0); return false; @@ -327,7 +328,7 @@ bool ODBCStatement::Fetch(size_t rows) { } if (current_ard_->HaveBindingsChanged()) { - // TODO: Deal handle when offset != buffer_length. + // GH-47871 TODO: handle when offset != buffer_length. // Wipe out all bindings in the ResultSet. // Note that the number of ARD records can both be more or less @@ -349,11 +350,24 @@ bool ODBCStatement::Fetch(size_t rows) { current_ard_->NotifyBindingsHavePropagated(); } - size_t rows_fetched = current_result_->Move(rows, current_ard_->GetBindOffset(), - current_ard_->GetBoundStructOffset(), - ird_->GetArrayStatusPtr()); + uint16_t* array_status_ptr; + if (row_status_array) { + // For SQLExtendedFetch only + array_status_ptr = row_status_array; + } else { + array_status_ptr = ird_->GetArrayStatusPtr(); + } + + size_t rows_fetched = + current_result_->Move(rows, current_ard_->GetBindOffset(), + current_ard_->GetBoundStructOffset(), array_status_ptr); ird_->SetRowsProcessed(static_cast(rows_fetched)); + if (row_count_ptr) { + // For SQLExtendedFetch only + *row_count_ptr = rows_fetched; + } + row_number_ += rows_fetched; has_reached_end_of_result_ = rows_fetched != rows; return rows_fetched != 0; @@ -522,7 +536,7 @@ void ODBCStatement::GetStmtAttr(SQLINTEGER statement_attribute, SQLPOINTER outpu } if (spi_attribute) { - GetAttribute(static_cast(boost::get(*spi_attribute)), output, + GetAttribute(static_cast(std::get(*spi_attribute)), output, buffer_size, str_len_ptr); return; } @@ -606,6 +620,7 @@ void ODBCStatement::SetStmtAttr(SQLINTEGER statement_attribute, 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"); @@ -729,7 +744,7 @@ SQLRETURN ODBCStatement::GetData(SQLSMALLINT record_number, SQLSMALLINT c_type, SQLSMALLINT evaluated_c_type = c_type; - // TODO: Get proper default precision and scale from abstraction. + // GH-47872 TODO: Get proper default precision and scale from abstraction. int precision = 38; // arrow::Decimal128Type::kMaxPrecision; int scale = 0; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h index 8bb448993f2..331b65cce76 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h @@ -17,9 +17,11 @@ #pragma once +// platform.h platform.h includes windows.h so it needs to be included first +#include "arrow/flight/sql/odbc/odbc_impl/platform.h" + #include "arrow/flight/sql/odbc/odbc_impl/odbc_handle.h" -#include #include #include #include @@ -59,7 +61,10 @@ class ODBCStatement : public ODBCHandle { void ExecuteDirect(const std::string& query); /// \brief Return true if the number of rows fetch was greater than zero. - bool Fetch(size_t rows); + /// + /// row_count_ptr and row_status_array are optional arguments, they are only needed for + /// SQLExtendedFetch + bool Fetch(size_t rows, SQLULEN* row_count_ptr = 0, SQLUSMALLINT* row_status_array = 0); bool IsPrepared() const; void GetStmtAttr(SQLINTEGER statement_attribute, SQLPOINTER output, diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/parse_table_types_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/parse_table_types_test.cc index cf1e5930a82..3749c276d4f 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/parse_table_types_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/parse_table_types_test.cc @@ -18,7 +18,8 @@ #include "arrow/flight/sql/odbc/odbc_impl/flight_sql_statement_get_tables.h" #include "arrow/flight/sql/odbc/odbc_impl/platform.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/record_batch_transformer.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/record_batch_transformer.h index 539583aac29..15548b52c63 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/record_batch_transformer.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/record_batch_transformer.h @@ -17,9 +17,9 @@ #pragma once -#include -#include #include +#include "arrow/flight/client.h" +#include "arrow/type.h" namespace arrow::flight::sql::odbc { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/record_batch_transformer_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/record_batch_transformer_test.cc index 9727167a500..a5e094317ea 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/record_batch_transformer_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/record_batch_transformer_test.cc @@ -20,7 +20,8 @@ #include "arrow/flight/sql/odbc/odbc_impl/platform.h" #include "arrow/record_batch.h" #include "arrow/testing/builder.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { namespace { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/scalar_function_reporter.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/scalar_function_reporter.h index f4855812bf9..e9cf18dc55a 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/scalar_function_reporter.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/scalar_function_reporter.h @@ -17,7 +17,7 @@ #pragma once -#include +#include "arrow/type.h" namespace arrow::flight::sql::odbc { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/connection.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/connection.h index 6e913cf2dba..fdfb2d2ea8b 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/connection.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/connection.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "arrow/flight/sql/odbc/odbc_impl/diagnostics.h" @@ -63,8 +64,8 @@ class Connection { PACKET_SIZE, // uint32_t - The Packet Size }; - typedef boost::variant Attribute; - typedef boost::variant Info; + typedef std::variant Attribute; + typedef std::variant Info; typedef PropertyMap ConnPropertyMap; /// \brief Establish the connection. diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set_metadata.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set_metadata.h index 38f81fc9c3e..a33784cc79b 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set_metadata.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/result_set_metadata.h @@ -17,9 +17,8 @@ #pragma once -#include "arrow/flight/sql/odbc/odbc_impl/types.h" - #include +#include "arrow/flight/sql/odbc/odbc_impl/types.h" namespace arrow::flight::sql::odbc { @@ -143,8 +142,9 @@ class ResultSetMetadata { /// \brief It returns the data type as a string. /// \param column_position [in] the position of the column, starting from 1. + /// \param data_type [in] the data type of the column. /// \return the data type string. - virtual std::string GetTypeName(int column_position) = 0; + virtual std::string GetTypeName(int column_position, int data_type) = 0; /// \brief It returns a numeric values indicate the updatability of the /// column. diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/statement.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/statement.h index 7278acd802a..e7a97d20a88 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/statement.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/statement.h @@ -17,9 +17,9 @@ #pragma once -#include #include #include +#include #include namespace arrow::flight::sql::odbc { @@ -37,7 +37,7 @@ class Statement { virtual ~Statement() = default; /// \brief Statement attributes that can be called at anytime. - ////TODO: Document attributes + /// GH-47850 TODO: Document attributes enum StatementAttributeId { MAX_LENGTH, // size_t - The maximum length when retrieving variable length data. 0 // means no limit. @@ -49,7 +49,7 @@ class Statement { // have no timeout. }; - typedef boost::variant Attribute; + typedef std::variant Attribute; /// \brief Set a statement attribute (may be called at any time) /// diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/custom_window.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/custom_window.cc index 179303b68e3..0d24f0cca82 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/custom_window.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/custom_window.cc @@ -25,6 +25,7 @@ #include #include +#include #include #include "arrow/flight/sql/odbc/odbc_impl/exceptions.h" diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.cc index 0432836a16f..be6b3fe4799 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.cc @@ -44,9 +44,9 @@ std::string TestConnection(const config::Configuration& config) { // This should have been checked before enabling the Test button. assert(missing_properties.empty()); std::string server_name = - boost::get(flight_sql_conn->GetInfo(SQL_SERVER_NAME)); + std::get(flight_sql_conn->GetInfo(SQL_SERVER_NAME)); std::string server_version = - boost::get(flight_sql_conn->GetInfo(SQL_DBMS_VER)); + std::get(flight_sql_conn->GetInfo(SQL_DBMS_VER)); return "Server Name: " + server_name + "\n" + "Server Version: " + server_version; } } // namespace @@ -565,7 +565,7 @@ bool DsnConfigurationWindow::OnMessage(UINT msg, WPARAM wparam, LPARAM lparam) { open_file_name.lpstrFile = file_name; open_file_name.lpstrFile[0] = '\0'; open_file_name.nMaxFile = FILENAME_MAX; - // TODO: What type should this be? + // GH-47851 TODO: Update `lpstrFilter` to correct value open_file_name.lpstrFilter = L"All\0*.*"; open_file_name.nFilterIndex = 1; open_file_name.lpstrFileTitle = NULL; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/window.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/window.cc index f21329977ba..ce10ddd3bf9 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/window.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/window.cc @@ -36,7 +36,6 @@ HINSTANCE GetHInstance() { TCHAR sz_file_name[MAX_PATH]; GetModuleFileName(NULL, sz_file_name, MAX_PATH); - // TODO: This needs to be the module name. HINSTANCE h_instance = GetModuleHandle(sz_file_name); if (h_instance == NULL) { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc index f06f00845a4..c23933287fb 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc @@ -56,6 +56,9 @@ SqlDataType GetDefaultSqlCharType(bool use_wide_char) { SqlDataType GetDefaultSqlVarcharType(bool use_wide_char) { return use_wide_char ? SqlDataType_WVARCHAR : SqlDataType_VARCHAR; } +SqlDataType GetDefaultSqlLongVarcharType(bool use_wide_char) { + return use_wide_char ? SqlDataType_WLONGVARCHAR : SqlDataType_LONGVARCHAR; +} CDataType GetDefaultCCharType(bool use_wide_char) { return use_wide_char ? CDataType_WCHAR : CDataType_CHAR; } @@ -114,12 +117,13 @@ SqlDataType GetDataTypeFromArrowFieldV3(const std::shared_ptr& field, case Type::TIME64: return SqlDataType_TYPE_TIME; case Type::INTERVAL_MONTHS: - return SqlDataType_INTERVAL_MONTH; // TODO: maybe - // SqlDataType_INTERVAL_YEAR_TO_MONTH + return SqlDataType_INTERVAL_MONTH; // GH-47873 TODO: check and update to + // SqlDataType_INTERVAL_YEAR_TO_MONTH if it is + // more appropriate case Type::INTERVAL_DAY_TIME: return SqlDataType_INTERVAL_DAY; - // TODO: Handle remaining types. + // GH-47873 TODO: Handle remaining types. case Type::INTERVAL_MONTH_DAY_NANO: case Type::LIST: case Type::STRUCT: @@ -147,6 +151,9 @@ SqlDataType EnsureRightSqlCharType(SqlDataType data_type, bool use_wide_char) { case SqlDataType_VARCHAR: case SqlDataType_WVARCHAR: return GetDefaultSqlVarcharType(use_wide_char); + case SqlDataType_LONGVARCHAR: + case SqlDataType_WLONGVARCHAR: + return GetDefaultSqlLongVarcharType(use_wide_char); default: return data_type; } @@ -664,7 +671,7 @@ optional GetDisplaySize(SqlDataType data_type, case SqlDataType_INTERVAL_HOUR_TO_MINUTE: case SqlDataType_INTERVAL_HOUR_TO_SECOND: case SqlDataType_INTERVAL_MINUTE_TO_SECOND: - return nullopt; // TODO: Implement for INTERVAL types + return nullopt; // GH-47874 TODO: Implement for INTERVAL types case SqlDataType_GUID: return 36; default: @@ -748,10 +755,12 @@ bool NeedArrayConversion(Type::type original_type_id, CDataType data_type) { return data_type != CDataType_BINARY; case Type::DECIMAL128: return data_type != CDataType_NUMERIC; + case Type::DURATION: case Type::LIST: case Type::LARGE_LIST: case Type::FIXED_SIZE_LIST: case Type::MAP: + case Type::STRING_VIEW: case Type::STRUCT: return data_type == CDataType_CHAR || data_type == CDataType_WCHAR; default: @@ -927,9 +936,9 @@ ArrayConvertTask GetConverter(Type::type original_type_id, CDataType target_type auto seconds_from_epoch = GetTodayTimeFromEpoch(); - auto third_converted_array = CheckConversion( - arrow::compute::Add(second_converted_array, - std::make_shared(seconds_from_epoch * 1000))); + auto third_converted_array = CheckConversion(arrow::compute::Add( + second_converted_array, + std::make_shared(seconds_from_epoch * 1000))); arrow::compute::CastOptions cast_options_2; cast_options_2.to_type = arrow::timestamp(TimeUnit::MILLI); @@ -948,7 +957,7 @@ ArrayConvertTask GetConverter(Type::type original_type_id, CDataType target_type auto second_converted_array = CheckConversion(arrow::compute::Add( first_converted_array, - std::make_shared(seconds_from_epoch * 1000000000))); + std::make_shared(seconds_from_epoch * 1000000000))); arrow::compute::CastOptions cast_options_2; cast_options_2.to_type = arrow::timestamp(TimeUnit::NANO); @@ -972,7 +981,7 @@ ArrayConvertTask GetConverter(Type::type original_type_id, CDataType target_type } else if (original_type_id == Type::DECIMAL128 && (target_type == CDataType_CHAR || target_type == CDataType_WCHAR)) { return [=](const std::shared_ptr& original_array) { - StringBuilder builder; + arrow::StringBuilder builder; int64_t length = original_array->length(); ThrowIfNotOK(builder.ReserveData(length)); diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h index f513c8d340d..11aa2f9913e 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h @@ -17,17 +17,31 @@ #pragma once -#include "arrow/util/utf8.h" +#include +#include +#include +#include +#include #include "arrow/flight/sql/odbc/odbc_impl/exceptions.h" #include "arrow/flight/sql/odbc/odbc_impl/spi/connection.h" #include "arrow/flight/sql/odbc/odbc_impl/types.h" #include "arrow/flight/types.h" +#include "arrow/util/utf8.h" -#include -#include -#include -#include +#define CONVERT_WIDE_STR(wstring_var, utf8_target) \ + wstring_var = [&] { \ + arrow::Result res = arrow::util::UTF8ToWideString(utf8_target); \ + arrow::flight::sql::odbc::util::ThrowIfNotOK(res.status()); \ + return res.ValueOrDie(); \ + }() + +#define CONVERT_UTF8_STR(string_var, wide_str_target) \ + string_var = [&] { \ + arrow::Result res = arrow::util::WideStringToUTF8(wide_str_target); \ + arrow::flight::sql::odbc::util::ThrowIfNotOK(res.status()); \ + return res.ValueOrDie(); \ + }() #define CONVERT_WIDE_STR(wstring_var, utf8_target) \ wstring_var = [&] { \ @@ -57,7 +71,7 @@ inline void ThrowIfNotOK(const Status& status) { template inline bool CheckIfSetToOnlyValidValue(const AttributeTypeT& value, T allowed_value) { - return boost::get(value) == allowed_value; + return std::get(value) == allowed_value; } template diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util_test.cc index bfcec15b4da..4946355ff20 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util_test.cc @@ -23,13 +23,21 @@ #include "arrow/testing/builder.h" #include "arrow/testing/gtest_util.h" #include "arrow/testing/util.h" -#include "gtest/gtest.h" + +#include namespace arrow::flight::sql::odbc { using util::ConvertSqlPatternToRegexString; using util::ConvertToDBMSVer; +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()); } +}; + // A global test "environment", to ensure Arrow compute kernel functions are registered class ComputeKernelEnvironment : public ::testing::Environment { @@ -48,7 +56,7 @@ void AssertConvertedArray(const std::shared_ptr& expected_array, ASSERT_EQ(expected_array->ToString(), converted_array->ToString()); } -std::shared_ptr convertArray(const std::shared_ptr& original_array, +std::shared_ptr ConvertArray(const std::shared_ptr& original_array, CDataType c_type) { auto converter = util::GetConverter(original_array->type_id(), c_type); return converter(original_array); @@ -60,7 +68,7 @@ void TestArrayConversion(const std::vector& input, std::shared_ptr original_array; ArrayFromVector(input, &original_array); - auto converted_array = convertArray(original_array, c_type); + auto converted_array = ConvertArray(original_array, c_type); AssertConvertedArray(expected_array, converted_array, input.size(), arrow_type); } @@ -71,7 +79,7 @@ void TestTime32ArrayConversion(const std::vector& input, std::shared_ptr original_array; ArrayFromVector(time32(TimeUnit::MILLI), input, &original_array); - auto converted_array = convertArray(original_array, c_type); + auto converted_array = ConvertArray(original_array, c_type); AssertConvertedArray(expected_array, converted_array, input.size(), arrow_type); } @@ -82,12 +90,12 @@ void TestTime64ArrayConversion(const std::vector& input, std::shared_ptr original_array; ArrayFromVector(time64(TimeUnit::NANO), input, &original_array); - auto converted_array = convertArray(original_array, c_type); + auto converted_array = ConvertArray(original_array, c_type); 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 = GetTodayTimeFromEpoch(); @@ -106,7 +114,7 @@ TEST(Utils, Time32ToTimeStampArray) { TestTime32ArrayConversion(input_data, expected, CDataType_TIMESTAMP, Type::TIMESTAMP); } -TEST(Utils, Time64ToTimeStampArray) { +TEST_F(UtilTestsWithCompute, Time64ToTimeStampArray) { std::vector input_data = {1579489200000, 1646881200000}; const auto seconds_from_epoch = GetTodayTimeFromEpoch(); @@ -125,7 +133,7 @@ TEST(Utils, Time64ToTimeStampArray) { TestTime64ArrayConversion(input_data, expected, CDataType_TIMESTAMP, Type::TIMESTAMP); } -TEST(Utils, StringToDateArray) { +TEST_F(UtilTestsWithCompute, StringToDateArray) { std::shared_ptr expected; ArrayFromVector({1579489200000, 1646881200000}, &expected); @@ -133,7 +141,7 @@ TEST(Utils, StringToDateArray) { Type::DATE64); } -TEST(Utils, StringToTimeArray) { +TEST_F(UtilTestsWithCompute, StringToTimeArray) { std::shared_ptr expected; ArrayFromVector(time64(TimeUnit::MICRO), {36000000000, 43200000000}, &expected); diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt index 39284c750b0..3041d2a973d 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt @@ -17,8 +17,11 @@ add_custom_target(tests) -find_package(ODBC REQUIRED) -include_directories(${ODBC_INCLUDE_DIRS}) +if(WIN32) + include_directories(${ODBC_INCLUDE_DIRS}) +else() + include_directories(${ODBC_INCLUDE_DIR}) +endif() find_package(SQLite3Alt REQUIRED) @@ -39,9 +42,11 @@ add_arrow_test(flight_sql_odbc_test connection_info_test.cc connection_test.cc errors_test.cc + get_functions_test.cc statement_attr_test.cc statement_test.cc tables_test.cc + type_info_test.cc # Enable Protobuf cleanup after test execution # GH-46889: move protobuf_test_util to a more common location ../../../../engine/substrait/protobuf_test_util.cc 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 00000000000..fe74c98b72f --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/README @@ -0,0 +1,23 @@ + + +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 index 81e97a09284..cb0ff6b3660 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/columns_test.cc @@ -124,6 +124,247 @@ void CheckRemoteSQLColumns( expected_is_nullable); } +void CheckSQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT idx, + const std::wstring& expected_column_name, + SQLLEN expected_data_type, SQLLEN expected_concise_type, + SQLLEN expected_display_size, SQLLEN expected_prec_scale, + SQLLEN expected_length, + const std::wstring& expected_literal_prefix, + const std::wstring& expected_literal_suffix, + SQLLEN expected_column_size, SQLLEN expected_column_scale, + SQLLEN expected_column_nullability, + SQLLEN expected_num_prec_radix, SQLLEN expected_octet_length, + SQLLEN expected_searchable, SQLLEN expected_unsigned_column) { + std::vector name(kOdbcBufferSize); + SQLSMALLINT name_len = 0; + std::vector base_column_name(kOdbcBufferSize); + SQLSMALLINT column_name_len = 0; + std::vector label(kOdbcBufferSize); + SQLSMALLINT label_len = 0; + std::vector prefix(kOdbcBufferSize); + SQLSMALLINT prefix_len = 0; + std::vector suffix(kOdbcBufferSize); + SQLSMALLINT suffix_len = 0; + SQLLEN data_type = 0; + SQLLEN concise_type = 0; + SQLLEN display_size = 0; + SQLLEN prec_scale = 0; + SQLLEN length = 0; + SQLLEN size = 0; + SQLLEN scale = 0; + SQLLEN nullability = 0; + SQLLEN num_prec_radix = 0; + SQLLEN octet_length = 0; + SQLLEN searchable = 0; + SQLLEN unsigned_col = 0; + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_NAME, &name[0], + (SQLSMALLINT)name.size(), &name_len, 0)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(stmt, idx, SQL_DESC_BASE_COLUMN_NAME, &base_column_name[0], + (SQLSMALLINT)base_column_name.size(), &column_name_len, 0)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_LABEL, &label[0], + (SQLSMALLINT)label.size(), &label_len, 0)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_TYPE, 0, 0, 0, &data_type)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(stmt, idx, SQL_DESC_CONCISE_TYPE, 0, 0, 0, &concise_type)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(stmt, idx, SQL_DESC_DISPLAY_SIZE, 0, 0, 0, &display_size)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(stmt, idx, SQL_DESC_FIXED_PREC_SCALE, 0, 0, 0, &prec_scale)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_LENGTH, 0, 0, 0, &length)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_LITERAL_PREFIX, &prefix[0], + (SQLSMALLINT)prefix.size(), &prefix_len, 0)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_LITERAL_SUFFIX, &suffix[0], + (SQLSMALLINT)suffix.size(), &suffix_len, 0)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_PRECISION, 0, 0, 0, &size)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_SCALE, 0, 0, 0, &scale)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(stmt, idx, SQL_DESC_NULLABLE, 0, 0, 0, &nullability)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, SQL_DESC_NUM_PREC_RADIX, 0, 0, 0, + &num_prec_radix)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(stmt, idx, SQL_DESC_OCTET_LENGTH, 0, 0, 0, &octet_length)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(stmt, idx, SQL_DESC_SEARCHABLE, 0, 0, 0, &searchable)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(stmt, idx, SQL_DESC_UNSIGNED, 0, 0, 0, &unsigned_col)); + + std::wstring name_str = ConvertToWString(name, name_len); + std::wstring base_column_name_str = ConvertToWString(base_column_name, column_name_len); + std::wstring label_str = ConvertToWString(label, label_len); + std::wstring prefixStr = ConvertToWString(prefix, prefix_len); + + // Assume column name, base column name, and label are equivalent in the result set + EXPECT_EQ(expected_column_name, name_str); + EXPECT_EQ(expected_column_name, base_column_name_str); + EXPECT_EQ(expected_column_name, label_str); + EXPECT_EQ(expected_data_type, data_type); + EXPECT_EQ(expected_concise_type, concise_type); + EXPECT_EQ(expected_display_size, display_size); + EXPECT_EQ(expected_prec_scale, prec_scale); + EXPECT_EQ(expected_length, length); + EXPECT_EQ(expected_literal_prefix, prefixStr); + EXPECT_EQ(expected_column_size, size); + EXPECT_EQ(expected_column_scale, scale); + EXPECT_EQ(expected_column_nullability, nullability); + EXPECT_EQ(expected_num_prec_radix, num_prec_radix); + EXPECT_EQ(expected_octet_length, octet_length); + EXPECT_EQ(expected_searchable, searchable); + EXPECT_EQ(expected_unsigned_column, unsigned_col); +} + +void CheckSQLColAttributes(SQLHSTMT stmt, SQLUSMALLINT idx, + const std::wstring& expected_column_name, + SQLLEN expected_data_type, SQLLEN expected_display_size, + SQLLEN expected_prec_scale, SQLLEN expected_length, + SQLLEN expected_column_size, SQLLEN expected_column_scale, + SQLLEN expected_column_nullability, SQLLEN expected_searchable, + SQLLEN expected_unsigned_column) { + std::vector name(kOdbcBufferSize); + SQLSMALLINT name_len = 0; + std::vector label(kOdbcBufferSize); + SQLSMALLINT label_len = 0; + SQLLEN data_type = 0; + SQLLEN display_size = 0; + SQLLEN prec_scale = 0; + SQLLEN length = 0; + SQLLEN size = 0; + SQLLEN scale = 0; + SQLLEN nullability = 0; + SQLLEN searchable = 0; + SQLLEN unsigned_col = 0; + + EXPECT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, SQL_COLUMN_NAME, &name[0], + (SQLSMALLINT)name.size(), &name_len, 0)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, SQL_COLUMN_LABEL, &label[0], + (SQLSMALLINT)label.size(), &label_len, 0)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttributes(stmt, idx, SQL_COLUMN_TYPE, 0, 0, 0, &data_type)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttributes(stmt, idx, SQL_COLUMN_DISPLAY_SIZE, 0, 0, 0, &display_size)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(stmt, idx, SQL_COLUMN_MONEY, 0, 0, 0, &prec_scale)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttributes(stmt, idx, SQL_COLUMN_LENGTH, 0, 0, 0, &length)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttributes(stmt, idx, SQL_COLUMN_PRECISION, 0, 0, 0, &size)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, SQL_COLUMN_SCALE, 0, 0, 0, &scale)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttributes(stmt, idx, SQL_COLUMN_NULLABLE, 0, 0, 0, &nullability)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttributes(stmt, idx, SQL_COLUMN_SEARCHABLE, 0, 0, 0, &searchable)); + + EXPECT_EQ(SQL_SUCCESS, + SQLColAttributes(stmt, idx, SQL_COLUMN_UNSIGNED, 0, 0, 0, &unsigned_col)); + + std::wstring name_str = ConvertToWString(name, name_len); + std::wstring label_str = ConvertToWString(label, label_len); + + EXPECT_EQ(expected_column_name, name_str); + EXPECT_EQ(expected_column_name, label_str); + EXPECT_EQ(expected_data_type, data_type); + EXPECT_EQ(expected_display_size, display_size); + EXPECT_EQ(expected_length, length); + EXPECT_EQ(expected_column_size, size); + EXPECT_EQ(expected_column_scale, scale); + EXPECT_EQ(expected_column_nullability, nullability); + EXPECT_EQ(expected_searchable, searchable); + EXPECT_EQ(expected_unsigned_column, unsigned_col); +} + +void GetSQLColAttributeString(SQLHSTMT stmt, const std::wstring& wsql, SQLUSMALLINT idx, + SQLUSMALLINT field_identifier, std::wstring& value) { + if (!wsql.empty()) { + // Execute query + std::vector sql0(wsql.begin(), wsql.end()); + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt)); + } + + // check SQLColAttribute string attribute + std::vector str_val(kOdbcBufferSize); + SQLSMALLINT str_len = 0; + + ASSERT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, field_identifier, &str_val[0], + (SQLSMALLINT)str_val.size(), &str_len, 0)); + + value = ConvertToWString(str_val, str_len); +} + +void GetSQLColAttributesString(SQLHSTMT stmt, const std::wstring& wsql, SQLUSMALLINT idx, + SQLUSMALLINT field_identifier, std::wstring& value) { + if (!wsql.empty()) { + // Execute query + std::vector sql0(wsql.begin(), wsql.end()); + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt)); + } + + // check SQLColAttribute string attribute + std::vector str_val(kOdbcBufferSize); + SQLSMALLINT str_len = 0; + + ASSERT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, field_identifier, &str_val[0], + (SQLSMALLINT)str_val.size(), &str_len, 0)); + + value = ConvertToWString(str_val, str_len); +} + +void GetSQLColAttributeNumeric(SQLHSTMT stmt, const std::wstring& wsql, SQLUSMALLINT idx, + SQLUSMALLINT field_identifier, SQLLEN* value) { + // Execute query and check SQLColAttribute numeric attribute + std::vector sql0(wsql.begin(), wsql.end()); + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt)); + + SQLLEN num_val = 0; + ASSERT_EQ(SQL_SUCCESS, SQLColAttribute(stmt, idx, field_identifier, 0, 0, 0, value)); +} + +void GetSQLColAttributesNumeric(SQLHSTMT stmt, const std::wstring& wsql, SQLUSMALLINT idx, + SQLUSMALLINT field_identifier, SQLLEN* value) { + // Execute query and check SQLColAttribute numeric attribute + std::vector sql0(wsql.begin(), wsql.end()); + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(stmt)); + + SQLLEN num_val = 0; + ASSERT_EQ(SQL_SUCCESS, SQLColAttributes(stmt, idx, field_identifier, 0, 0, 0, value)); +} } // namespace TYPED_TEST(ColumnsTest, SQLColumnsTestInputData) { @@ -659,7 +900,7 @@ TEST_F(ColumnsRemoteTest, TestSQLColumnsAllTypes) { EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); } -TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColumnsAllTypesODBCVer2) { +TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColumnsAllTypes) { // GH-47159 TODO: Return NUM_PREC_RADIX based on whether COLUMN_SIZE contains number of // digits or bits @@ -861,7 +1102,7 @@ TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColumnsAllTypesODBCVer2) { EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); } -TEST_F(ColumnsMockTest, TestSQLColumnsColumnPattern) { +TEST_F(ColumnsMockTest, TestSQLColumnsColumnPatternSegFault) { // Checks filtering table with column name pattern. // Only check table and column name @@ -960,4 +1201,1522 @@ TEST_F(ColumnsMockTest, TestSQLColumnsInvalidTablePattern) { EXPECT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); } +TYPED_TEST(ColumnsTest, SQLColAttributeTestInputData) { + std::wstring wsql = L"SELECT 1 as col1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + SQLUSMALLINT idx = 1; + std::vector character_attr(kOdbcBufferSize); + SQLSMALLINT character_attr_len = 0; + SQLLEN numeric_attr = 0; + + // All character values populated + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(this->stmt, idx, SQL_DESC_NAME, &character_attr[0], + (SQLSMALLINT)character_attr.size(), &character_attr_len, 0)); + + // All numeric values populated + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(this->stmt, idx, SQL_DESC_COUNT, 0, 0, 0, &numeric_attr)); + + // Pass null values, driver should not throw error + EXPECT_EQ(SQL_SUCCESS, + SQLColAttribute(this->stmt, idx, SQL_COLUMN_TABLE_NAME, 0, 0, 0, 0)); + + EXPECT_EQ(SQL_SUCCESS, SQLColAttribute(this->stmt, idx, SQL_DESC_COUNT, 0, 0, 0, 0)); +} + +TYPED_TEST(ColumnsTest, SQLColAttributeGetCharacterLen) { + std::wstring wsql = L"SELECT 1 as col1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + SQLSMALLINT character_attr_len = 0; + + // Check length of character attribute + ASSERT_EQ(SQL_SUCCESS, SQLColAttribute(this->stmt, 1, SQL_DESC_BASE_COLUMN_NAME, 0, 0, + &character_attr_len, 0)); + EXPECT_EQ(4 * GetSqlWCharSize(), character_attr_len); +} + +TYPED_TEST(ColumnsTest, SQLColAttributeInvalidFieldId) { + std::wstring wsql = L"SELECT 1 as col1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + SQLUSMALLINT invalid_field_id = -100; + SQLUSMALLINT idx = 1; + std::vector character_attr(kOdbcBufferSize); + SQLSMALLINT character_attr_len = 0; + SQLLEN numeric_attr = 0; + + ASSERT_EQ(SQL_ERROR, + SQLColAttribute(this->stmt, idx, invalid_field_id, &character_attr[0], + (SQLSMALLINT)character_attr.size(), &character_attr_len, 0)); + // Verify invalid descriptor field identifier error state is returned + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY091); +} + +TYPED_TEST(ColumnsTest, SQLColAttributeInvalidColId) { + std::wstring wsql = L"SELECT 1 as col1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + SQLUSMALLINT invalid_col_id = 2; + std::vector character_attr(kOdbcBufferSize); + SQLSMALLINT character_attr_len = 0; + + ASSERT_EQ(SQL_ERROR, + SQLColAttribute(this->stmt, invalid_col_id, SQL_DESC_BASE_COLUMN_NAME, + &character_attr[0], (SQLSMALLINT)character_attr.size(), + &character_attr_len, 0)); + // Verify invalid descriptor index error state is returned + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState07009); +} + +TEST_F(ColumnsMockTest, TestSQLColAttributeAllTypes) { + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLColAttribute(this->stmt, 1, + std::wstring(L"bigint_col"), // expected_column_name + SQL_BIGINT, // expected_data_type + SQL_BIGINT, // expected_concise_type + 20, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 10, // expected_num_prec_radix + 8, // expected_octet_length + SQL_PRED_NONE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 2, + std::wstring(L"char_col"), // expected_column_name + SQL_WVARCHAR, // expected_data_type + SQL_WVARCHAR, // expected_concise_type + 0, // expected_display_size + SQL_FALSE, // expected_prec_scale + 0, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 0, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 0, // expected_octet_length + SQL_PRED_NONE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 3, + std::wstring(L"varbinary_col"), // expected_column_name + SQL_BINARY, // expected_data_type + SQL_BINARY, // expected_concise_type + 0, // expected_display_size + SQL_FALSE, // expected_prec_scale + 0, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 0, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 0, // expected_octet_length + SQL_PRED_NONE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 4, + std::wstring(L"double_col"), // expected_column_name + SQL_DOUBLE, // expected_data_type + SQL_DOUBLE, // expected_concise_type + 24, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 2, // expected_num_prec_radix + 8, // expected_octet_length + SQL_PRED_NONE, // expected_searchable + SQL_FALSE); // expected_unsigned_column +} + +TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesAllTypes) { + // Tests ODBC 2.0 API SQLColAttributes + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + CheckSQLColAttributes(this->stmt, 1, + std::wstring(L"bigint_col"), // expected_column_name + SQL_BIGINT, // expected_data_type + 20, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_PRED_NONE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 2, + std::wstring(L"char_col"), // expected_column_name + SQL_WVARCHAR, // expected_data_type + 0, // expected_display_size + SQL_FALSE, // expected_prec_scale + 0, // expected_length + 0, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_PRED_NONE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 3, + std::wstring(L"varbinary_col"), // expected_column_name + SQL_BINARY, // expected_data_type + 0, // expected_display_size + SQL_FALSE, // expected_prec_scale + 0, // expected_length + 0, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_PRED_NONE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 4, + std::wstring(L"double_col"), // expected_column_name + SQL_DOUBLE, // expected_data_type + 24, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_PRED_NONE, // expected_searchable + SQL_FALSE); // expected_unsigned_column +} + +TEST_F(ColumnsRemoteTest, TestSQLColAttributeAllTypes) { + // Test assumes there is a table $scratch.ODBCTest in remote server + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLColAttribute(this->stmt, 1, + std::wstring(L"sinteger_max"), // expected_column_name + SQL_INTEGER, // expected_data_type + SQL_INTEGER, // expected_concise_type + 11, // expected_display_size + SQL_FALSE, // expected_prec_scale + 4, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 4, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 10, // expected_num_prec_radix + 4, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 2, + std::wstring(L"sbigint_max"), // expected_column_name + SQL_BIGINT, // expected_data_type + SQL_BIGINT, // expected_concise_type + 20, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 10, // expected_num_prec_radix + 8, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 3, + std::wstring(L"decimal_positive"), // expected_column_name + SQL_DECIMAL, // expected_data_type + SQL_DECIMAL, // expected_concise_type + 40, // expected_display_size + SQL_FALSE, // expected_prec_scale + 19, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 19, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 10, // expected_num_prec_radix + 40, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 4, + std::wstring(L"float_max"), // expected_column_name + SQL_FLOAT, // expected_data_type + SQL_FLOAT, // expected_concise_type + 24, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 2, // expected_num_prec_radix + 8, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 5, + std::wstring(L"double_max"), // expected_column_name + SQL_DOUBLE, // expected_data_type + SQL_DOUBLE, // expected_concise_type + 24, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 2, // expected_num_prec_radix + 8, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 6, + std::wstring(L"bit_true"), // expected_column_name + SQL_BIT, // expected_data_type + SQL_BIT, // expected_concise_type + 1, // expected_display_size + SQL_FALSE, // expected_prec_scale + 1, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 1, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 1, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 7, + std::wstring(L"date_max"), // expected_column_name + SQL_DATETIME, // expected_data_type + SQL_TYPE_DATE, // expected_concise_type + 10, // expected_display_size + SQL_FALSE, // expected_prec_scale + 10, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 10, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 6, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 8, + std::wstring(L"time_max"), // expected_column_name + SQL_DATETIME, // expected_data_type + SQL_TYPE_TIME, // expected_concise_type + 12, // expected_display_size + SQL_FALSE, // expected_prec_scale + 12, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 12, // expected_column_size + 3, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 6, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 9, + std::wstring(L"timestamp_max"), // expected_column_name + SQL_DATETIME, // expected_data_type + SQL_TYPE_TIMESTAMP, // expected_concise_type + 23, // expected_display_size + SQL_FALSE, // expected_prec_scale + 23, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 23, // expected_column_size + 3, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 16, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column +} + +TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributeAllTypes) { + // Test assumes there is a table $scratch.ODBCTest in remote server + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLColAttribute(this->stmt, 1, + std::wstring(L"sinteger_max"), // expected_column_name + SQL_INTEGER, // expected_data_type + SQL_INTEGER, // expected_concise_type + 11, // expected_display_size + SQL_FALSE, // expected_prec_scale + 4, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 4, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 10, // expected_num_prec_radix + 4, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 2, + std::wstring(L"sbigint_max"), // expected_column_name + SQL_BIGINT, // expected_data_type + SQL_BIGINT, // expected_concise_type + 20, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 10, // expected_num_prec_radix + 8, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 3, + std::wstring(L"decimal_positive"), // expected_column_name + SQL_DECIMAL, // expected_data_type + SQL_DECIMAL, // expected_concise_type + 40, // expected_display_size + SQL_FALSE, // expected_prec_scale + 19, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 19, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 10, // expected_num_prec_radix + 40, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 4, + std::wstring(L"float_max"), // expected_column_name + SQL_FLOAT, // expected_data_type + SQL_FLOAT, // expected_concise_type + 24, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 2, // expected_num_prec_radix + 8, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 5, + std::wstring(L"double_max"), // expected_column_name + SQL_DOUBLE, // expected_data_type + SQL_DOUBLE, // expected_concise_type + 24, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 2, // expected_num_prec_radix + 8, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 6, + std::wstring(L"bit_true"), // expected_column_name + SQL_BIT, // expected_data_type + SQL_BIT, // expected_concise_type + 1, // expected_display_size + SQL_FALSE, // expected_prec_scale + 1, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 1, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 1, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 7, + std::wstring(L"date_max"), // expected_column_name + SQL_DATETIME, // expected_data_type + SQL_DATE, // expected_concise_type + 10, // expected_display_size + SQL_FALSE, // expected_prec_scale + 10, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 10, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 6, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 8, + std::wstring(L"time_max"), // expected_column_name + SQL_DATETIME, // expected_data_type + SQL_TIME, // expected_concise_type + 12, // expected_display_size + SQL_FALSE, // expected_prec_scale + 12, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 12, // expected_column_size + 3, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 6, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttribute(this->stmt, 9, + std::wstring(L"timestamp_max"), // expected_column_name + SQL_DATETIME, // expected_data_type + SQL_TIMESTAMP, // expected_concise_type + 23, // expected_display_size + SQL_FALSE, // expected_prec_scale + 23, // expected_length + std::wstring(L""), // expected_literal_prefix + std::wstring(L""), // expected_literal_suffix + 23, // expected_column_size + 3, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + 0, // expected_num_prec_radix + 16, // expected_octet_length + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column +} + +TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributesAllTypes) { + // Tests ODBC 2.0 API SQLColAttributes + // Test assumes there is a table $scratch.ODBCTest in remote server + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLColAttributes(this->stmt, 1, + std::wstring(L"sinteger_max"), // expected_column_name + SQL_INTEGER, // expected_data_type + 11, // expected_display_size + SQL_FALSE, // expected_prec_scale + 4, // expected_length + 4, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 2, + std::wstring(L"sbigint_max"), // expected_column_name + SQL_BIGINT, // expected_data_type + 20, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 3, + std::wstring(L"decimal_positive"), // expected_column_name + SQL_DECIMAL, // expected_data_type + 40, // expected_display_size + SQL_FALSE, // expected_prec_scale + 19, // expected_length + 19, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 4, + std::wstring(L"float_max"), // expected_column_name + SQL_FLOAT, // expected_data_type + 24, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 5, + std::wstring(L"double_max"), // expected_column_name + SQL_DOUBLE, // expected_data_type + 24, // expected_display_size + SQL_FALSE, // expected_prec_scale + 8, // expected_length + 8, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 6, + std::wstring(L"bit_true"), // expected_column_name + SQL_BIT, // expected_data_type + 1, // expected_display_size + SQL_FALSE, // expected_prec_scale + 1, // expected_length + 1, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 7, + std::wstring(L"date_max"), // expected_column_name + SQL_DATE, // expected_data_type + 10, // expected_display_size + SQL_FALSE, // expected_prec_scale + 10, // expected_length + 10, // expected_column_size + 0, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 8, + std::wstring(L"time_max"), // expected_column_name + SQL_TIME, // expected_data_type + 12, // expected_display_size + SQL_FALSE, // expected_prec_scale + 12, // expected_length + 12, // expected_column_size + 3, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column + + CheckSQLColAttributes(this->stmt, 9, + std::wstring(L"timestamp_max"), // expected_column_name + SQL_TIMESTAMP, // expected_data_type + 23, // expected_display_size + SQL_FALSE, // expected_prec_scale + 23, // expected_length + 23, // expected_column_size + 3, // expected_column_scale + SQL_NULLABLE, // expected_column_nullability + SQL_SEARCHABLE, // expected_searchable + SQL_TRUE); // expected_unsigned_column +} + +TYPED_TEST(ColumnsTest, TestSQLColAttributeCaseSensitive) { + // Arrow limitation: returns SQL_FALSE for case sensitive column + + std::wstring wsql = this->GetQueryAllDataTypes(); + // Int column + SQLLEN value; + GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_CASE_SENSITIVE, &value); + ASSERT_EQ(SQL_FALSE, value); + SQLFreeStmt(this->stmt, SQL_CLOSE); + // Varchar column + GetSQLColAttributeNumeric(this->stmt, wsql, 28, SQL_DESC_CASE_SENSITIVE, &value); + ASSERT_EQ(SQL_FALSE, value); +} + +TYPED_TEST(ColumnsOdbcV2Test, TestSQLColAttributesCaseSensitive) { + // Arrow limitation: returns SQL_FALSE for case sensitive column + // Tests ODBC 2.0 API SQLColAttributes + + std::wstring wsql = this->GetQueryAllDataTypes(); + // Int column + SQLLEN value; + GetSQLColAttributesNumeric(this->stmt, wsql, 1, SQL_COLUMN_CASE_SENSITIVE, &value); + ASSERT_EQ(SQL_FALSE, value); + SQLFreeStmt(this->stmt, SQL_CLOSE); + // Varchar column + GetSQLColAttributesNumeric(this->stmt, wsql, 28, SQL_COLUMN_CASE_SENSITIVE, &value); + ASSERT_EQ(SQL_FALSE, value); +} + +TEST_F(ColumnsMockTest, TestSQLColAttributeUniqueValue) { + // Mock server limitation: returns false for auto-increment column + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + SQLLEN value; + GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_AUTO_UNIQUE_VALUE, &value); + ASSERT_EQ(SQL_FALSE, value); +} + +TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesAutoIncrement) { + // Tests ODBC 2.0 API SQLColAttributes + // Mock server limitation: returns false for auto-increment column + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + SQLLEN value; + GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_COLUMN_AUTO_INCREMENT, &value); + ASSERT_EQ(SQL_FALSE, value); +} + +TEST_F(ColumnsMockTest, TestSQLColAttributeBaseTableName) { + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_BASE_TABLE_NAME, value); + ASSERT_EQ(std::wstring(L"AllTypesTable"), value); +} + +TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesTableName) { + // Tests ODBC 2.0 API SQLColAttributes + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::wstring value; + GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TABLE_NAME, value); + ASSERT_EQ(std::wstring(L"AllTypesTable"), value); +} + +TEST_F(ColumnsMockTest, TestSQLColAttributeCatalogName) { + // Mock server limitattion: mock doesn't return catalog for result metadata, + // and the defautl catalog should be 'main' + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_CATALOG_NAME, value); + ASSERT_EQ(std::wstring(L""), value); +} + +TEST_F(ColumnsRemoteTest, TestSQLColAttributeCatalogName) { + // Remote server does not have catalogs + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_CATALOG_NAME, value); + ASSERT_EQ(std::wstring(L""), value); +} + +TEST_F(ColumnsOdbcV2MockTest, 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->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_COLUMN_QUALIFIER_NAME, value); + ASSERT_EQ(std::wstring(L""), value); +} + +TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributesQualifierName) { + // Remote server does not have catalogs + // Tests ODBC 2.0 API SQLColAttributes + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_COLUMN_QUALIFIER_NAME, value); + ASSERT_EQ(std::wstring(L""), value); +} + +TYPED_TEST(ColumnsTest, TestSQLColAttributeCount) { + std::wstring wsql = this->GetQueryAllDataTypes(); + // Pass 0 as column number, driver should ignore it + SQLLEN value; + GetSQLColAttributeNumeric(this->stmt, wsql, 0, SQL_DESC_COUNT, &value); + ASSERT_EQ(32, value); +} + +TEST_F(ColumnsMockTest, TestSQLColAttributeLocalTypeName) { + std::wstring wsql = this->GetQueryAllDataTypes(); + // Mock server doesn't have local type name + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_LOCAL_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L""), value); +} + +TEST_F(ColumnsRemoteTest, TestSQLColAttributeLocalTypeName) { + std::wstring wsql = this->GetQueryAllDataTypes(); + std::wstring value; + GetSQLColAttributesString(this->stmt, wsql, 1, SQL_DESC_LOCAL_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"INTEGER"), value); +} + +TEST_F(ColumnsMockTest, TestSQLColAttributeSchemaName) { + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't have schemas + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_SCHEMA_NAME, value); + ASSERT_EQ(std::wstring(L""), value); +} + +TEST_F(ColumnsRemoteTest, TestSQLColAttributeSchemaName) { + // Test assumes there is a table $scratch.ODBCTest in remote server + + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + // Remote server limitation: doesn't return schema name, expected schema name is + // $scratch + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_SCHEMA_NAME, value); + ASSERT_EQ(std::wstring(L""), value); +} + +TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesOwnerName) { + // Tests ODBC 2.0 API SQLColAttributes + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't have schemas + std::wstring value; + GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_OWNER_NAME, value); + ASSERT_EQ(std::wstring(L""), value); +} + +TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributesOwnerName) { + // Test assumes there is a table $scratch.ODBCTest in remote server + // Tests ODBC 2.0 API SQLColAttributes + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + // Remote server limitation: doesn't return schema name, expected schema name is + // $scratch + std::wstring value; + GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_OWNER_NAME, value); + ASSERT_EQ(std::wstring(L""), value); +} + +TEST_F(ColumnsMockTest, TestSQLColAttributeTableName) { + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TABLE_NAME, value); + ASSERT_EQ(std::wstring(L"AllTypesTable"), value); +} + +TEST_F(ColumnsMockTest, TestSQLColAttributeTypeName) { + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"BIGINT"), value); + GetSQLColAttributeString(this->stmt, L"", 2, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"WVARCHAR"), value); + GetSQLColAttributeString(this->stmt, L"", 3, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"BINARY"), value); + GetSQLColAttributeString(this->stmt, L"", 4, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"DOUBLE"), value); +} + +TEST_F(ColumnsRemoteTest, TestSQLColAttributeTypeName) { + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::wstring value; + GetSQLColAttributeString(this->stmt, wsql, 1, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"INTEGER"), value); + GetSQLColAttributeString(this->stmt, L"", 2, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"BIGINT"), value); + GetSQLColAttributeString(this->stmt, L"", 3, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"DECIMAL"), value); + GetSQLColAttributeString(this->stmt, L"", 4, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"FLOAT"), value); + GetSQLColAttributeString(this->stmt, L"", 5, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"DOUBLE"), value); + GetSQLColAttributeString(this->stmt, L"", 6, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"BOOLEAN"), value); + GetSQLColAttributeString(this->stmt, L"", 7, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"DATE"), value); + GetSQLColAttributeString(this->stmt, L"", 8, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"TIME"), value); + GetSQLColAttributeString(this->stmt, L"", 9, SQL_DESC_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"TIMESTAMP"), value); +} + +TEST_F(ColumnsOdbcV2MockTest, TestSQLColAttributesTypeName) { + // Tests ODBC 2.0 API SQLColAttributes + this->CreateTableAllDataType(); + + std::wstring wsql = L"SELECT * from AllTypesTable;"; + // Mock server doesn't return data source-dependent data type name + std::wstring value; + GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"BIGINT"), value); + GetSQLColAttributesString(this->stmt, L"", 2, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"WVARCHAR"), value); + GetSQLColAttributesString(this->stmt, L"", 3, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"BINARY"), value); + GetSQLColAttributesString(this->stmt, L"", 4, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"DOUBLE"), value); +} + +TEST_F(ColumnsOdbcV2RemoteTest, TestSQLColAttributesTypeName) { + // Tests ODBC 2.0 API SQLColAttributes + std::wstring wsql = L"SELECT * from $scratch.ODBCTest;"; + std::wstring value; + GetSQLColAttributesString(this->stmt, wsql, 1, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"INTEGER"), value); + GetSQLColAttributesString(this->stmt, L"", 2, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"BIGINT"), value); + GetSQLColAttributesString(this->stmt, L"", 3, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"DECIMAL"), value); + GetSQLColAttributesString(this->stmt, L"", 4, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"FLOAT"), value); + GetSQLColAttributesString(this->stmt, L"", 5, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"DOUBLE"), value); + GetSQLColAttributesString(this->stmt, L"", 6, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"BOOLEAN"), value); + GetSQLColAttributesString(this->stmt, L"", 7, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"DATE"), value); + GetSQLColAttributesString(this->stmt, L"", 8, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"TIME"), value); + GetSQLColAttributesString(this->stmt, L"", 9, SQL_COLUMN_TYPE_NAME, value); + ASSERT_EQ(std::wstring(L"TIMESTAMP"), value); +} + +TYPED_TEST(ColumnsTest, TestSQLColAttributeUnnamed) { + std::wstring wsql = this->GetQueryAllDataTypes(); + SQLLEN value; + GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_UNNAMED, &value); + ASSERT_EQ(SQL_NAMED, value); +} + +TYPED_TEST(ColumnsTest, TestSQLColAttributeUpdatable) { + std::wstring wsql = this->GetQueryAllDataTypes(); + // Mock server and remote server do not return updatable information + SQLLEN value; + GetSQLColAttributeNumeric(this->stmt, wsql, 1, SQL_DESC_UPDATABLE, &value); + ASSERT_EQ(SQL_ATTR_READWRITE_UNKNOWN, value); +} + +TYPED_TEST(ColumnsOdbcV2Test, TestSQLColAttributesUpdatable) { + // Tests ODBC 2.0 API SQLColAttributes + std::wstring wsql = this->GetQueryAllDataTypes(); + // Mock server and remote server do not return updatable information + SQLLEN value; + GetSQLColAttributesNumeric(this->stmt, wsql, 1, SQL_COLUMN_UPDATABLE, &value); + ASSERT_EQ(SQL_ATTR_READWRITE_UNKNOWN, value); +} + +TEST_F(ColumnsMockTest, TestSQLDescribeColValidateInput) { + this->CreateTestTables(); + + SQLWCHAR sql_query[] = L"SELECT * FROM TestTable LIMIT 1;"; + SQLINTEGER query_length = static_cast(wcslen(sql_query)); + + SQLUSMALLINT bookmark_column = 0; + SQLUSMALLINT out_of_range_column = 4; + SQLUSMALLINT negative_column = -1; + SQLWCHAR column_name[1024] = {0}; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + + ASSERT_EQ(SQL_SUCCESS, SQLExecDirect(this->stmt, sql_query, query_length)); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Invalid descriptor index - Bookmarks are not supported + EXPECT_EQ(SQL_ERROR, SQLDescribeCol(this->stmt, bookmark_column, column_name, + buf_char_len, &name_length, &data_type, + &column_size, &decimal_digits, &nullable)); +#ifdef __APPLE__ + // non-standard odbc error code for invalid column index + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateS1002); +#else + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState07009); +#endif // __APPLE__ + + // Invalid descriptor index - index out of range + EXPECT_EQ(SQL_ERROR, SQLDescribeCol(this->stmt, out_of_range_column, column_name, + buf_char_len, &name_length, &data_type, + &column_size, &decimal_digits, &nullable)); + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState07009); + + // Invalid descriptor index - index out of range + EXPECT_EQ(SQL_ERROR, SQLDescribeCol(this->stmt, negative_column, column_name, + buf_char_len, &name_length, &data_type, + &column_size, &decimal_digits, &nullable)); + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState07009); +} + +TEST_F(ColumnsMockTest, SQLDescribeColQueryAllDataTypesMetadata) { + // Mock server has a limitation where only SQL_WVARCHAR column type values are returned + // from SELECT AS queries + + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 0; + + std::wstring wsql = this->GetQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + const SQLWCHAR* column_names[] = {static_cast(L"stiny_int_min"), + static_cast(L"stiny_int_max"), + static_cast(L"utiny_int_min"), + static_cast(L"utiny_int_max"), + static_cast(L"ssmall_int_min"), + static_cast(L"ssmall_int_max"), + static_cast(L"usmall_int_min"), + static_cast(L"usmall_int_max"), + static_cast(L"sinteger_min"), + static_cast(L"sinteger_max"), + static_cast(L"uinteger_min"), + static_cast(L"uinteger_max"), + static_cast(L"sbigint_min"), + static_cast(L"sbigint_max"), + static_cast(L"ubigint_min"), + static_cast(L"ubigint_max"), + static_cast(L"decimal_negative"), + static_cast(L"decimal_positive"), + static_cast(L"float_min"), + static_cast(L"float_max"), + static_cast(L"double_min"), + static_cast(L"double_max"), + static_cast(L"bit_false"), + static_cast(L"bit_true"), + static_cast(L"c_char"), + static_cast(L"c_wchar"), + static_cast(L"c_wvarchar"), + static_cast(L"c_varchar"), + static_cast(L"date_min"), + static_cast(L"date_max"), + static_cast(L"timestamp_min"), + static_cast(L"timestamp_max")}; + SQLSMALLINT column_data_types[] = { + SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_WVARCHAR}; + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + column_index = i + 1; + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(wcslen(column_names[i]), name_length); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(column_names[i], returned); + EXPECT_EQ(column_data_types[i], column_data_type); + EXPECT_EQ(1024, column_size); + EXPECT_EQ(0, decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); + + name_length = 0; + column_data_type = 0; + column_size = 0; + decimal_digits = 0; + nullable = 0; + } +} + +TEST_F(ColumnsRemoteTest, SQLDescribeColQueryAllDataTypesMetadata) { + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 0; + + std::wstring wsql = this->GetQueryAllDataTypes(); + std::vector sql0(wsql.begin(), wsql.end()); + + const SQLWCHAR* column_names[] = {static_cast(L"stiny_int_min"), + static_cast(L"stiny_int_max"), + static_cast(L"utiny_int_min"), + static_cast(L"utiny_int_max"), + static_cast(L"ssmall_int_min"), + static_cast(L"ssmall_int_max"), + static_cast(L"usmall_int_min"), + static_cast(L"usmall_int_max"), + static_cast(L"sinteger_min"), + static_cast(L"sinteger_max"), + static_cast(L"uinteger_min"), + static_cast(L"uinteger_max"), + static_cast(L"sbigint_min"), + static_cast(L"sbigint_max"), + static_cast(L"ubigint_min"), + static_cast(L"ubigint_max"), + static_cast(L"decimal_negative"), + static_cast(L"decimal_positive"), + static_cast(L"float_min"), + static_cast(L"float_max"), + static_cast(L"double_min"), + static_cast(L"double_max"), + static_cast(L"bit_false"), + static_cast(L"bit_true"), + static_cast(L"c_char"), + static_cast(L"c_wchar"), + static_cast(L"c_wvarchar"), + static_cast(L"c_varchar"), + static_cast(L"date_min"), + static_cast(L"date_max"), + static_cast(L"timestamp_min"), + static_cast(L"timestamp_max")}; + SQLSMALLINT column_data_types[] = { + SQL_INTEGER, SQL_INTEGER, SQL_INTEGER, SQL_INTEGER, SQL_INTEGER, + SQL_INTEGER, SQL_INTEGER, SQL_INTEGER, SQL_INTEGER, SQL_INTEGER, + SQL_BIGINT, SQL_BIGINT, SQL_BIGINT, SQL_BIGINT, SQL_BIGINT, + SQL_WVARCHAR, SQL_DECIMAL, SQL_DECIMAL, SQL_FLOAT, SQL_FLOAT, + SQL_DOUBLE, SQL_DOUBLE, SQL_BIT, SQL_BIT, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_TYPE_DATE, SQL_TYPE_DATE, + SQL_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP}; + SQLULEN column_sizes[] = {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, + 8, 8, 8, 8, 65536, 19, 19, 8, 8, 8, 8, + 1, 1, 65536, 65536, 65536, 65536, 10, 10, 23, 23}; + SQLULEN column_decimal_digits[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 23, 23}; + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + column_index = i + 1; + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(wcslen(column_names[i]), name_length); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(column_names[i], returned); + EXPECT_EQ(column_data_types[i], column_data_type); + EXPECT_EQ(column_sizes[i], column_size); + EXPECT_EQ(column_decimal_digits[i], decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); + + name_length = 0; + column_data_type = 0; + column_size = 0; + decimal_digits = 0; + nullable = 0; + } +} + +TEST_F(ColumnsRemoteTest, SQLDescribeColODBCTestTableMetadata) { + // Test assumes there is a table $scratch.ODBCTest in remote server + + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 0; + + SQLWCHAR sql_query[] = L"SELECT * from $scratch.ODBCTest LIMIT 1;"; + SQLINTEGER query_length = static_cast(wcslen(sql_query)); + + const SQLWCHAR* column_names[] = {static_cast(L"sinteger_max"), + static_cast(L"sbigint_max"), + static_cast(L"decimal_positive"), + static_cast(L"float_max"), + static_cast(L"double_max"), + static_cast(L"bit_true"), + static_cast(L"date_max"), + static_cast(L"time_max"), + static_cast(L"timestamp_max")}; + SQLSMALLINT column_data_types[] = {SQL_INTEGER, SQL_BIGINT, SQL_DECIMAL, + SQL_FLOAT, SQL_DOUBLE, SQL_BIT, + SQL_TYPE_DATE, SQL_TYPE_TIME, SQL_TYPE_TIMESTAMP}; + SQLULEN column_sizes[] = {4, 8, 19, 8, 8, 1, 10, 12, 23}; + SQLULEN columndecimal_digits[] = {0, 0, 0, 0, 0, 0, 10, 12, 23}; + + ASSERT_EQ(SQL_SUCCESS, SQLExecDirect(this->stmt, sql_query, query_length)); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + column_index = i + 1; + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(wcslen(column_names[i]), name_length); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(column_names[i], returned); + EXPECT_EQ(column_data_types[i], column_data_type); + EXPECT_EQ(column_sizes[i], column_size); + EXPECT_EQ(columndecimal_digits[i], decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); + + name_length = 0; + column_data_type = 0; + column_size = 0; + decimal_digits = 0; + nullable = 0; + } +} + +TEST_F(ColumnsOdbcV2RemoteTest, SQLDescribeColODBCTestTableMetadataODBC2) { + // Test assumes there is a table $scratch.ODBCTest in remote server + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 0; + + SQLWCHAR sql_query[] = L"SELECT * from $scratch.ODBCTest LIMIT 1;"; + SQLINTEGER query_length = static_cast(wcslen(sql_query)); + + const SQLWCHAR* column_names[] = {static_cast(L"sinteger_max"), + static_cast(L"sbigint_max"), + static_cast(L"decimal_positive"), + static_cast(L"float_max"), + static_cast(L"double_max"), + static_cast(L"bit_true"), + static_cast(L"date_max"), + static_cast(L"time_max"), + static_cast(L"timestamp_max")}; + SQLSMALLINT column_data_types[] = {SQL_INTEGER, SQL_BIGINT, SQL_DECIMAL, + SQL_FLOAT, SQL_DOUBLE, SQL_BIT, + SQL_DATE, SQL_TIME, SQL_TIMESTAMP}; + SQLULEN column_sizes[] = {4, 8, 19, 8, 8, 1, 10, 12, 23}; + SQLULEN columndecimal_digits[] = {0, 0, 0, 0, 0, 0, 10, 12, 23}; + + ASSERT_EQ(SQL_SUCCESS, SQLExecDirect(this->stmt, sql_query, query_length)); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + column_index = i + 1; + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(wcslen(column_names[i]), name_length); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(column_names[i], returned); + EXPECT_EQ(column_data_types[i], column_data_type); + EXPECT_EQ(column_sizes[i], column_size); + EXPECT_EQ(columndecimal_digits[i], decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); + + name_length = 0; + column_data_type = 0; + column_size = 0; + decimal_digits = 0; + nullable = 0; + } +} + +TEST_F(ColumnsMockTest, SQLDescribeColAllTypesTableMetadata) { + this->CreateTableAllDataType(); + + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 0; + + SQLWCHAR sql_query[] = L"SELECT * from AllTypesTable LIMIT 1;"; + SQLINTEGER query_length = static_cast(wcslen(sql_query)); + + const SQLWCHAR* column_names[] = {static_cast(L"bigint_col"), + static_cast(L"char_col"), + static_cast(L"varbinary_col"), + static_cast(L"double_col")}; + SQLSMALLINT column_data_types[] = {SQL_BIGINT, SQL_WVARCHAR, SQL_BINARY, SQL_DOUBLE}; + SQLULEN column_sizes[] = {8, 0, 0, 8}; + + ASSERT_EQ(SQL_SUCCESS, SQLExecDirect(this->stmt, sql_query, query_length)); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + column_index = i + 1; + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(wcslen(column_names[i]), name_length); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(column_names[i], returned); + EXPECT_EQ(column_data_types[i], column_data_type); + EXPECT_EQ(column_sizes[i], column_size); + EXPECT_EQ(0, decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); + + name_length = 0; + column_data_type = 0; + column_size = 0; + decimal_digits = 0; + nullable = 0; + } +} + +TEST_F(ColumnsMockTest, SQLDescribeColUnicodeTableMetadata) { + this->CreateUnicodeTable(); + + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 1; + + SQLWCHAR sql_query[] = L"SELECT * from 数据 LIMIT 1;"; + SQLINTEGER query_length = static_cast(wcslen(sql_query)); + + SQLWCHAR expected_column_name[] = L"资料"; + SQLSMALLINT expected_column_data_type = SQL_WVARCHAR; + SQLULEN expected_column_size = 0; + + ASSERT_EQ(SQL_SUCCESS, SQLExecDirect(this->stmt, sql_query, query_length)); + + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(name_length, wcslen(expected_column_name)); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(returned, expected_column_name); + EXPECT_EQ(column_data_type, expected_column_data_type); + EXPECT_EQ(column_size, expected_column_size); + EXPECT_EQ(0, decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); +} + +TYPED_TEST(ColumnsTest, SQLColumnsGetMetadataBySQLDescribeCol) { + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 0; + + const SQLWCHAR* column_names[] = {static_cast(L"TABLE_CAT"), + static_cast(L"TABLE_SCHEM"), + static_cast(L"TABLE_NAME"), + static_cast(L"COLUMN_NAME"), + static_cast(L"DATA_TYPE"), + static_cast(L"TYPE_NAME"), + static_cast(L"COLUMN_SIZE"), + static_cast(L"BUFFER_LENGTH"), + static_cast(L"DECIMAL_DIGITS"), + static_cast(L"NUM_PREC_RADIX"), + static_cast(L"NULLABLE"), + static_cast(L"REMARKS"), + static_cast(L"COLUMN_DEF"), + static_cast(L"SQL_DATA_TYPE"), + static_cast(L"SQL_DATETIME_SUB"), + static_cast(L"CHAR_OCTET_LENGTH"), + static_cast(L"ORDINAL_POSITION"), + static_cast(L"IS_NULLABLE")}; + SQLSMALLINT column_data_types[] = { + SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_SMALLINT, SQL_WVARCHAR, + SQL_INTEGER, SQL_INTEGER, SQL_SMALLINT, SQL_SMALLINT, SQL_SMALLINT, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_SMALLINT, SQL_SMALLINT, SQL_INTEGER, SQL_INTEGER, SQL_WVARCHAR}; + SQLULEN column_sizes[] = {1024, 1024, 1024, 1024, 2, 1024, 4, 4, 2, + 2, 2, 1024, 1024, 2, 2, 4, 4, 1024}; + + ASSERT_EQ(SQL_SUCCESS, + SQLColumns(this->stmt, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0)); + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + column_index = i + 1; + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(wcslen(column_names[i]), name_length); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(column_names[i], returned); + EXPECT_EQ(column_data_types[i], column_data_type); + EXPECT_EQ(column_sizes[i], column_size); + EXPECT_EQ(0, decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); + + name_length = 0; + column_data_type = 0; + column_size = 0; + decimal_digits = 0; + nullable = 0; + } +} + +TYPED_TEST(ColumnsOdbcV2Test, SQLColumnsGetMetadataBySQLDescribeColODBC2) { + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 0; + + const SQLWCHAR* column_names[] = {static_cast(L"TABLE_QUALIFIER"), + static_cast(L"TABLE_OWNER"), + static_cast(L"TABLE_NAME"), + static_cast(L"COLUMN_NAME"), + static_cast(L"DATA_TYPE"), + static_cast(L"TYPE_NAME"), + static_cast(L"PRECISION"), + static_cast(L"LENGTH"), + static_cast(L"SCALE"), + static_cast(L"RADIX"), + static_cast(L"NULLABLE"), + static_cast(L"REMARKS"), + static_cast(L"COLUMN_DEF"), + static_cast(L"SQL_DATA_TYPE"), + static_cast(L"SQL_DATETIME_SUB"), + static_cast(L"CHAR_OCTET_LENGTH"), + static_cast(L"ORDINAL_POSITION"), + static_cast(L"IS_NULLABLE")}; + SQLSMALLINT column_data_types[] = { + SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, SQL_SMALLINT, SQL_WVARCHAR, + SQL_INTEGER, SQL_INTEGER, SQL_SMALLINT, SQL_SMALLINT, SQL_SMALLINT, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_SMALLINT, SQL_SMALLINT, SQL_INTEGER, SQL_INTEGER, SQL_WVARCHAR}; + SQLULEN column_sizes[] = {1024, 1024, 1024, 1024, 2, 1024, 4, 4, 2, + 2, 2, 1024, 1024, 2, 2, 4, 4, 1024}; + + ASSERT_EQ(SQL_SUCCESS, + SQLColumns(this->stmt, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0)); + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + column_index = i + 1; + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(wcslen(column_names[i]), name_length); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(column_names[i], returned); + EXPECT_EQ(column_data_types[i], column_data_type); + EXPECT_EQ(column_sizes[i], column_size); + EXPECT_EQ(0, decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); + + name_length = 0; + column_data_type = 0; + column_size = 0; + decimal_digits = 0; + nullable = 0; + } +} } // 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 index 413bc43c0ad..cc56d767ab6 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/connection_attr_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_attr_test.cc @@ -176,7 +176,7 @@ TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrTraceFileDMOnly) { kOdbcBufferSize, &out_str_len)); // Length is returned in bytes for SQLGetConnectAttr, // we want the number of characters - out_str_len /= arrow::flight::sql::odbc::GetSqlWCharSize(); + out_str_len /= GetSqlWCharSize(); std::string out_connection_string = ODBC::SqlWcharToString(out_str, static_cast(out_str_len)); EXPECT_FALSE(out_connection_string.empty()); diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc index b1081bc1d6a..3ca4a50ef76 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc @@ -442,7 +442,7 @@ TEST_F(ConnectionRemoteTest, TestSQLDriverConnectInvalidUid) { arrow::util::UTF8ToWideString(connect_str)); std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); - SQLWCHAR out_str[kOdbcBufferSize]; + SQLWCHAR out_str[kOdbcBufferSize] = {0}; SQLSMALLINT out_str_len; // Connecting to ODBC server. diff --git a/cpp/src/arrow/flight/sql/odbc/tests/dremio/docker-compose.yml b/cpp/src/arrow/flight/sql/odbc/tests/dremio/docker-compose.yml new file mode 100644 index 00000000000..eaab4d02b73 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/dremio/docker-compose.yml @@ -0,0 +1,35 @@ +# 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. + +# GH-48068 TODO: run remote ODBC tests on Linux + +services: + dremio: + platform: linux/x86_64 + image: dremio/dremio-oss:latest + ports: + - 9047:9047 # REST API + - 31010:31010 # JDBC/ODBC + - 32010:32010 + container_name: dremio_container + environment: + - DREMIO_JAVA_SERVER_EXTRA_OPTS=-Dsaffron.default.charset=UTF-8 -Dsaffron.default.nationalcharset=UTF-8 -Dsaffron.default.collation.name=UTF-8$$en_US + healthcheck: + test: curl --fail http://localhost:9047 || exit 1 + interval: 10s + timeout: 5s + retries: 30 diff --git a/cpp/src/arrow/flight/sql/odbc/tests/dremio/set_up_dremio_instance.sh b/cpp/src/arrow/flight/sql/odbc/tests/dremio/set_up_dremio_instance.sh new file mode 100644 index 00000000000..8d632bb2c3e --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/dremio/set_up_dremio_instance.sh @@ -0,0 +1,66 @@ +# 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. + +# GH-48068 TODO: run remote ODBC tests on Linux + +#!/bin/bash +set -e + +HOST_URL="http://localhost:9047" +NEW_USER_URL="$HOST_URL/apiv2/bootstrap/firstuser" +LOGIN_URL="$HOST_URL/apiv2/login" +SQL_URL="$HOST_URL/api/v3/sql" + +ADMIN_USER="admin" +ADMIN_PASSWORD="admin2025" + +# Wait for Dremio to be available. +until curl -s "$NEW_USER_URL"; do + echo 'Waiting for Dremio to start...' + sleep 5 +done + +echo "" +echo 'Creating admin user...' + +# Create new admin account. +curl -X PUT "$NEW_USER_URL" \ + -H "Content-Type: application/json" \ + -d "{ \"userName\": \"$ADMIN_USER\", \"password\": \"$ADMIN_PASSWORD\" }" + +echo "" +echo "Created admin user." + +# Use admin account to login and acquire a token. +TOKEN=$(curl -s -X POST "$LOGIN_URL" \ + -H "Content-Type: application/json" \ + -d "{ \"userName\": \"$ADMIN_USER\", \"password\": \"$ADMIN_PASSWORD\" }" \ + | grep -oP '(?<="token":")[^"]+') + +SQL_QUERY="Create Table \$scratch.ODBCTest As SELECT CAST(2147483647 AS INTEGER) AS sinteger_max, CAST(9223372036854775807 AS BIGINT) AS sbigint_max, CAST(999999999 AS DECIMAL(38,0)) AS decimal_positive, CAST(3.40282347E38 AS FLOAT) AS float_max, CAST(1.7976931348623157E308 AS DOUBLE) AS double_max, CAST(true AS BOOLEAN) AS bit_true, CAST(DATE '9999-12-31' AS DATE) AS date_max, CAST(TIME '23:59:59' AS TIME) AS time_max, CAST(TIMESTAMP '9999-12-31 23:59:59' AS TIMESTAMP) AS timestamp_max;" +ESCAPED_QUERY=$(printf '%s' "$SQL_QUERY" | sed 's/"/\\"/g') + +echo "Creating \$scratch.ODBCTest table." + +# Create a new table by sending a SQL query. +curl -i -X POST "$SQL_URL" \ + -H "Authorization: _dremio$TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"sql\": \"$ESCAPED_QUERY\"}" + +echo "" +echo "Finished setting up dremio docker instance." diff --git a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc index 34a32738455..b38e10a6ce8 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc @@ -47,6 +47,8 @@ using TestTypesHandle = ::testing::Types; TYPED_TEST_SUITE(ErrorsHandleTest, TestTypesHandle); +using ODBC::SqlWcharToString; + TYPED_TEST(ErrorsHandleTest, TestSQLGetDiagFieldWForConnectFailure) { // Invalid connect string std::string connect_str = this->GetInvalidConnectionString(); @@ -90,9 +92,12 @@ TYPED_TEST(ErrorsHandleTest, TestSQLGetDiagFieldWForConnectFailure) { SQLWCHAR message_text[kOdbcBufferSize]; SQLSMALLINT message_text_length; - EXPECT_EQ(SQL_SUCCESS, - SQLGetDiagField(SQL_HANDLE_DBC, this->conn, RECORD_1, SQL_DIAG_MESSAGE_TEXT, - message_text, kOdbcBufferSize, &message_text_length)); + SQLRETURN ret = + SQLGetDiagField(SQL_HANDLE_DBC, this->conn, RECORD_1, SQL_DIAG_MESSAGE_TEXT, + message_text, kOdbcBufferSize, &message_text_length); + + // dependent on the size of the message it could output SQL_SUCCESS_WITH_INFO + EXPECT_TRUE(ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO); EXPECT_GT(message_text_length, 100); @@ -114,10 +119,9 @@ TYPED_TEST(ErrorsHandleTest, TestSQLGetDiagFieldWForConnectFailure) { EXPECT_EQ( SQL_SUCCESS, SQLGetDiagField(SQL_HANDLE_DBC, this->conn, RECORD_1, SQL_DIAG_SQLSTATE, sql_state, - sql_state_size * arrow::flight::sql::odbc::GetSqlWCharSize(), - &sql_state_length)); + sql_state_size * GetSqlWCharSize(), &sql_state_length)); - EXPECT_EQ(std::wstring(L"28000"), std::wstring(sql_state)); + EXPECT_EQ(kErrorState28000, SqlWcharToString(sql_state)); } TYPED_TEST(ErrorsHandleTest, DISABLED_TestSQLGetDiagFieldWForConnectFailureNTS) { @@ -216,7 +220,7 @@ TYPED_TEST(ErrorsTest, TestSQLGetDiagFieldWForDescriptorFailureFromDriverManager SQLGetDiagField(SQL_HANDLE_DESC, descriptor, RECORD_1, SQL_DIAG_SQLSTATE, sql_state, sql_state_size * GetSqlWCharSize(), &sql_state_length)); - EXPECT_EQ(std::wstring(L"IM001"), std::wstring(sql_state)); + EXPECT_EQ(kErrorStateIM001, SqlWcharToString(sql_state)); // Free descriptor handle EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, descriptor)); @@ -245,7 +249,7 @@ TYPED_TEST(ErrorsTest, TestSQLGetDiagRecForDescriptorFailureFromDriverManager) { EXPECT_EQ(0, native_error); // API not implemented error from driver manager - EXPECT_EQ(std::wstring(L"IM001"), std::wstring(sql_state)); + EXPECT_EQ(kErrorStateIM001, SqlWcharToString(sql_state)); EXPECT_FALSE(std::wstring(message).empty()); @@ -282,7 +286,7 @@ TYPED_TEST(ErrorsHandleTest, TestSQLGetDiagRecForConnectFailure) { EXPECT_EQ(200, native_error); - EXPECT_EQ(std::wstring(L"28000"), std::wstring(sql_state)); + EXPECT_EQ(kErrorState28000, SqlWcharToString(sql_state)); EXPECT_FALSE(std::wstring(message).empty()); } @@ -305,11 +309,17 @@ TYPED_TEST(ErrorsTest, TestSQLGetDiagRecInputData) { nullptr, 0, nullptr)); // Invalid handle +#ifdef __APPLE__ + // MacOS ODBC driver manager requires connection handle + EXPECT_EQ(SQL_INVALID_HANDLE, + SQLGetDiagRec(0, this->conn, 1, nullptr, nullptr, nullptr, 0, nullptr)); +#else EXPECT_EQ(SQL_INVALID_HANDLE, SQLGetDiagRec(0, nullptr, 0, nullptr, nullptr, nullptr, 0, nullptr)); +#endif // __APPLE__ } -TYPED_TEST(ErrorsTest, TestSQLErrorInputData) { +TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorInputData) { // Test ODBC 2.0 API SQLError. Driver manager maps SQLError to SQLGetDiagRec. // SQLError does not post diagnostic records for itself. @@ -320,8 +330,13 @@ TYPED_TEST(ErrorsTest, TestSQLErrorInputData) { EXPECT_EQ(SQL_NO_DATA, SQLError(nullptr, this->conn, nullptr, nullptr, nullptr, nullptr, 0, nullptr)); +#ifdef __APPLE__ + EXPECT_EQ(SQL_NO_DATA, SQLError(SQL_NULL_HENV, this->conn, this->stmt, nullptr, nullptr, + nullptr, 0, nullptr)); +#else EXPECT_EQ(SQL_NO_DATA, SQLError(nullptr, nullptr, this->stmt, nullptr, nullptr, nullptr, 0, nullptr)); +#endif // __APPLE__ // Invalid handle EXPECT_EQ(SQL_INVALID_HANDLE, @@ -345,12 +360,12 @@ TYPED_TEST(ErrorsTest, TestSQLErrorEnvErrorFromDriverManager) { ASSERT_EQ(SQL_SUCCESS, SQLError(this->env, nullptr, nullptr, sql_state, &native_error, message, SQL_MAX_MESSAGE_LENGTH, &message_length)); - EXPECT_GT(message_length, 50); + EXPECT_GT(message_length, 40); EXPECT_EQ(0, native_error); // Function sequence error state from driver manager - EXPECT_EQ(std::wstring(L"HY010"), std::wstring(sql_state)); + EXPECT_EQ(kErrorStateHY010, SqlWcharToString(sql_state)); EXPECT_FALSE(std::wstring(message).empty()); } @@ -362,9 +377,8 @@ TYPED_TEST(ErrorsTest, TestSQLErrorConnError) { // DM passes 512 as buffer length to SQLError. // Attempt to set unsupported attribute - SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, 0, 0, nullptr); - - ASSERT_EQ(SQL_ERROR, ret); + ASSERT_EQ(SQL_ERROR, + SQLGetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, 0, 0, nullptr)); SQLWCHAR sql_state[6] = {0}; SQLINTEGER native_error = 0; @@ -378,7 +392,7 @@ TYPED_TEST(ErrorsTest, TestSQLErrorConnError) { EXPECT_EQ(100, native_error); // optional feature not supported error state - EXPECT_EQ(std::wstring(L"HYC00"), std::wstring(sql_state)); + EXPECT_EQ(kErrorStateHYC00, SqlWcharToString(sql_state)); EXPECT_FALSE(std::wstring(message).empty()); } @@ -399,14 +413,16 @@ TYPED_TEST(ErrorsTest, TestSQLErrorStmtError) { SQLINTEGER native_error = 0; SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; SQLSMALLINT message_length = 0; - ASSERT_EQ(SQL_SUCCESS, SQLError(nullptr, nullptr, this->stmt, sql_state, &native_error, - message, SQL_MAX_MESSAGE_LENGTH, &message_length)); + SQLRETURN ret = SQLError(nullptr, this->conn, this->stmt, sql_state, &native_error, + message, SQL_MAX_MESSAGE_LENGTH, &message_length); + + EXPECT_TRUE(ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO); EXPECT_GT(message_length, 70); EXPECT_EQ(100, native_error); - EXPECT_EQ(std::wstring(L"HY000"), std::wstring(sql_state)); + EXPECT_EQ(kErrorStateHY000, SqlWcharToString(sql_state)); EXPECT_FALSE(std::wstring(message).empty()); } @@ -434,20 +450,21 @@ TYPED_TEST(ErrorsTest, TestSQLErrorStmtWarning) { SQLINTEGER native_error = 0; SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; SQLSMALLINT message_length = 0; - ASSERT_EQ(SQL_SUCCESS, SQLError(nullptr, nullptr, this->stmt, sql_state, &native_error, - message, SQL_MAX_MESSAGE_LENGTH, &message_length)); + ASSERT_EQ(SQL_SUCCESS, + SQLError(SQL_NULL_HENV, this->conn, this->stmt, sql_state, &native_error, + message, SQL_MAX_MESSAGE_LENGTH, &message_length)); EXPECT_GT(message_length, 50); EXPECT_EQ(1000100, native_error); // Verify string truncation warning is reported - EXPECT_EQ(std::wstring(L"01004"), std::wstring(sql_state)); + EXPECT_EQ(kErrorState01004, SqlWcharToString(sql_state)); EXPECT_FALSE(std::wstring(message).empty()); } -TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorEnvErrorODBCVer2FromDriverManager) { +TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorEnvErrorFromDriverManager) { // Test ODBC 2.0 API SQLError with ODBC ver 2. // Known Windows Driver Manager (DM) behavior: // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), @@ -464,22 +481,35 @@ TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorEnvErrorODBCVer2FromDriverManager) { ASSERT_EQ(SQL_SUCCESS, SQLError(this->env, nullptr, nullptr, sql_state, &native_error, message, SQL_MAX_MESSAGE_LENGTH, &message_length)); - EXPECT_GT(message_length, 50); + EXPECT_GT(message_length, 40); EXPECT_EQ(0, native_error); // Function sequence error state from driver manager - EXPECT_EQ(std::wstring(L"S1010"), std::wstring(sql_state)); +#ifdef _WIN32 + // Windows Driver Manager returns S1010 + EXPECT_EQ(kErrorStateS1010, SqlWcharToString(sql_state)); +#else + // unix Driver Manager returns HY010 + EXPECT_EQ(kErrorStateHY010, SqlWcharToString(sql_state)); +#endif // _WIN32 EXPECT_FALSE(std::wstring(message).empty()); } -TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorConnErrorODBCVer2) { +// TODO: verify that `SQLGetConnectOption` is not required by Excel. +#ifndef __APPLE__ +TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorConnError) { // Test ODBC 2.0 API SQLError with ODBC ver 2. // Known Windows Driver Manager (DM) behavior: // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), // DM passes 512 as buffer length to SQLError. + // Known macOS Driver Manager (DM) behavior: + // Attempts to call SQLGetConnectOption without redirecting the API call to + // SQLGetConnectAttr. SQLGetConnectOption is not implemented as it is not required by + // macOS Excel. + // Attempt to set unsupported attribute ASSERT_EQ(SQL_ERROR, SQLGetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, 0, 0, nullptr)); @@ -496,12 +526,13 @@ TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorConnErrorODBCVer2) { EXPECT_EQ(100, native_error); // optional feature not supported error state. Driver Manager maps state to S1C00 - EXPECT_EQ(std::wstring(L"S1C00"), std::wstring(sql_state)); + EXPECT_EQ(kErrorStateS1C00, SqlWcharToString(sql_state)); EXPECT_FALSE(std::wstring(message).empty()); } +#endif // __APPLE__ -TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorStmtErrorODBCVer2) { +TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorStmtError) { // Test ODBC 2.0 API SQLError with ODBC ver 2. // Known Windows Driver Manager (DM) behavior: // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), @@ -525,12 +556,12 @@ TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorStmtErrorODBCVer2) { EXPECT_EQ(100, native_error); // Driver Manager maps error state to S1000 - EXPECT_EQ(std::wstring(L"S1000"), std::wstring(sql_state)); + EXPECT_EQ(kErrorStateS1000, SqlWcharToString(sql_state)); EXPECT_FALSE(std::wstring(message).empty()); } -TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorStmtWarningODBCVer2) { +TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorStmtWarning) { // Test ODBC 2.0 API SQLError. std::wstring wsql = L"SELECT 'VERY LONG STRING here' AS string_col;"; @@ -553,15 +584,16 @@ TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorStmtWarningODBCVer2) { SQLINTEGER native_error = 0; SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; SQLSMALLINT message_length = 0; - ASSERT_EQ(SQL_SUCCESS, SQLError(nullptr, nullptr, this->stmt, sql_state, &native_error, - message, SQL_MAX_MESSAGE_LENGTH, &message_length)); + ASSERT_EQ(SQL_SUCCESS, + SQLError(SQL_NULL_HENV, this->conn, this->stmt, sql_state, &native_error, + message, SQL_MAX_MESSAGE_LENGTH, &message_length)); EXPECT_GT(message_length, 50); EXPECT_EQ(1000100, native_error); // Verify string truncation warning is reported - EXPECT_EQ(std::wstring(L"01004"), std::wstring(sql_state)); + EXPECT_EQ(kErrorState01004, SqlWcharToString(sql_state)); EXPECT_FALSE(std::wstring(message).empty()); } diff --git a/cpp/src/arrow/flight/sql/odbc/tests/get_functions_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/get_functions_test.cc new file mode 100644 index 00000000000..3b47b80cf05 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/get_functions_test.cc @@ -0,0 +1,220 @@ +// 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/odbc_impl/platform.h" + +#include +#include +#include + +#include + +namespace arrow::flight::sql::odbc { + +template +class GetFunctionsTest : public T {}; + +using TestTypes = + ::testing::Types; +TYPED_TEST_SUITE(GetFunctionsTest, TestTypes); + +template +class GetFunctionsOdbcV2Test : public T {}; + +using TestTypesOdbcV2 = + ::testing::Types; +TYPED_TEST_SUITE(GetFunctionsOdbcV2Test, TestTypesOdbcV2); + +TYPED_TEST(GetFunctionsTest, TestSQLGetFunctionsAllFunctions) { + // Verify driver manager return values for SQLGetFunctions + + SQLUSMALLINT api_exists[SQL_API_ODBC3_ALL_FUNCTIONS_SIZE]; + const std::vector supported_functions = { + SQL_API_SQLALLOCHANDLE, SQL_API_SQLBINDCOL, SQL_API_SQLGETDIAGFIELD, + SQL_API_SQLCANCEL, SQL_API_SQLCLOSECURSOR, SQL_API_SQLGETDIAGREC, + SQL_API_SQLCOLATTRIBUTE, SQL_API_SQLGETENVATTR, SQL_API_SQLCONNECT, + SQL_API_SQLGETINFO, SQL_API_SQLGETSTMTATTR, SQL_API_SQLDESCRIBECOL, + SQL_API_SQLGETTYPEINFO, SQL_API_SQLDISCONNECT, SQL_API_SQLNUMRESULTCOLS, + SQL_API_SQLPREPARE, SQL_API_SQLEXECDIRECT, SQL_API_SQLEXECUTE, SQL_API_SQLROWCOUNT, + SQL_API_SQLFETCH, SQL_API_SQLSETCONNECTATTR, SQL_API_SQLFETCHSCROLL, + SQL_API_SQLFREEHANDLE, SQL_API_SQLFREESTMT, SQL_API_SQLGETCONNECTATTR, + SQL_API_SQLSETENVATTR, SQL_API_SQLSETSTMTATTR, SQL_API_SQLGETDATA, + SQL_API_SQLCOLUMNS, SQL_API_SQLTABLES, SQL_API_SQLNATIVESQL, + SQL_API_SQLDRIVERCONNECT, SQL_API_SQLMORERESULTS, SQL_API_SQLPRIMARYKEYS, + SQL_API_SQLFOREIGNKEYS, + + // ODBC 2.0 APIs + SQL_API_SQLSETSTMTOPTION, SQL_API_SQLGETSTMTOPTION, SQL_API_SQLSETCONNECTOPTION, + SQL_API_SQLGETCONNECTOPTION, SQL_API_SQLALLOCCONNECT, SQL_API_SQLALLOCENV, + SQL_API_SQLALLOCSTMT, SQL_API_SQLFREEENV, SQL_API_SQLFREECONNECT, + + // Driver Manager APIs + SQL_API_SQLGETFUNCTIONS, SQL_API_SQLDRIVERS, SQL_API_SQLDATASOURCES}; + const std::vector unsupported_functions = { + SQL_API_SQLPUTDATA, SQL_API_SQLGETDESCFIELD, SQL_API_SQLGETDESCREC, + SQL_API_SQLCOPYDESC, SQL_API_SQLPARAMDATA, SQL_API_SQLENDTRAN, + SQL_API_SQLSETCURSORNAME, SQL_API_SQLSETDESCFIELD, SQL_API_SQLSETDESCREC, + SQL_API_SQLGETCURSORNAME, SQL_API_SQLSTATISTICS, SQL_API_SQLSPECIALCOLUMNS, + SQL_API_SQLBINDPARAMETER, SQL_API_SQLBROWSECONNECT, SQL_API_SQLNUMPARAMS, + SQL_API_SQLBULKOPERATIONS, SQL_API_SQLCOLUMNPRIVILEGES, SQL_API_SQLPROCEDURECOLUMNS, + SQL_API_SQLDESCRIBEPARAM, SQL_API_SQLPROCEDURES, SQL_API_SQLSETPOS, + SQL_API_SQLTABLEPRIVILEGES}; + + ASSERT_EQ(SQL_SUCCESS, + SQLGetFunctions(this->conn, SQL_API_ODBC3_ALL_FUNCTIONS, api_exists)); + + for (int api : supported_functions) { + EXPECT_EQ(SQL_TRUE, SQL_FUNC_EXISTS(api_exists, api)); + } + + for (int api : unsupported_functions) { + EXPECT_EQ(SQL_FALSE, SQL_FUNC_EXISTS(api_exists, api)); + } +} + +TYPED_TEST(GetFunctionsOdbcV2Test, TestSQLGetFunctionsAllFunctions) { + // Verify driver manager return values for SQLGetFunctions + + // ODBC 2.0 SQLGetFunctions returns 100 elements according to spec + SQLUSMALLINT api_exists[100]; + const std::vector supported_functions = { + SQL_API_SQLCONNECT, SQL_API_SQLGETINFO, SQL_API_SQLDESCRIBECOL, + SQL_API_SQLGETTYPEINFO, SQL_API_SQLDISCONNECT, SQL_API_SQLNUMRESULTCOLS, + SQL_API_SQLPREPARE, SQL_API_SQLEXECDIRECT, SQL_API_SQLEXECUTE, SQL_API_SQLROWCOUNT, + SQL_API_SQLFETCH, SQL_API_SQLFREESTMT, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS, + SQL_API_SQLTABLES, SQL_API_SQLNATIVESQL, SQL_API_SQLDRIVERCONNECT, + SQL_API_SQLMORERESULTS, SQL_API_SQLSETSTMTOPTION, SQL_API_SQLGETSTMTOPTION, + SQL_API_SQLSETCONNECTOPTION, SQL_API_SQLGETCONNECTOPTION, SQL_API_SQLALLOCCONNECT, + SQL_API_SQLALLOCENV, SQL_API_SQLALLOCSTMT, SQL_API_SQLFREEENV, + SQL_API_SQLFREECONNECT, SQL_API_SQLPRIMARYKEYS, SQL_API_SQLFOREIGNKEYS, + + // Driver Manager APIs + SQL_API_SQLGETFUNCTIONS, SQL_API_SQLDRIVERS, SQL_API_SQLDATASOURCES}; + const std::vector unsupported_functions = { + SQL_API_SQLPUTDATA, SQL_API_SQLPARAMDATA, SQL_API_SQLSETCURSORNAME, + SQL_API_SQLGETCURSORNAME, SQL_API_SQLSTATISTICS, SQL_API_SQLSPECIALCOLUMNS, + SQL_API_SQLBINDPARAMETER, SQL_API_SQLBROWSECONNECT, SQL_API_SQLNUMPARAMS, + SQL_API_SQLBULKOPERATIONS, SQL_API_SQLCOLUMNPRIVILEGES, SQL_API_SQLPROCEDURECOLUMNS, + SQL_API_SQLDESCRIBEPARAM, SQL_API_SQLPROCEDURES, SQL_API_SQLSETPOS, + SQL_API_SQLTABLEPRIVILEGES}; + ASSERT_EQ(SQL_SUCCESS, SQLGetFunctions(this->conn, SQL_API_ALL_FUNCTIONS, api_exists)); + + for (int api : supported_functions) { + EXPECT_EQ(SQL_TRUE, api_exists[api]); + } + + for (int api : unsupported_functions) { + EXPECT_EQ(SQL_FALSE, api_exists[api]); + } +} + +TYPED_TEST(GetFunctionsTest, TestSQLGetFunctionsSupportedSingleAPI) { + const std::vector supported_functions = { + SQL_API_SQLALLOCHANDLE, SQL_API_SQLBINDCOL, SQL_API_SQLGETDIAGFIELD, + SQL_API_SQLCANCEL, SQL_API_SQLCLOSECURSOR, SQL_API_SQLGETDIAGREC, + SQL_API_SQLCOLATTRIBUTE, SQL_API_SQLGETENVATTR, SQL_API_SQLCONNECT, + SQL_API_SQLGETINFO, SQL_API_SQLGETSTMTATTR, SQL_API_SQLDESCRIBECOL, + SQL_API_SQLGETTYPEINFO, SQL_API_SQLDISCONNECT, SQL_API_SQLNUMRESULTCOLS, + SQL_API_SQLPREPARE, SQL_API_SQLEXECDIRECT, SQL_API_SQLEXECUTE, SQL_API_SQLROWCOUNT, + SQL_API_SQLFETCH, SQL_API_SQLSETCONNECTATTR, SQL_API_SQLFETCHSCROLL, + SQL_API_SQLFREEHANDLE, SQL_API_SQLFREESTMT, SQL_API_SQLGETCONNECTATTR, + SQL_API_SQLSETENVATTR, SQL_API_SQLSETSTMTATTR, SQL_API_SQLGETDATA, + SQL_API_SQLCOLUMNS, SQL_API_SQLTABLES, SQL_API_SQLNATIVESQL, + SQL_API_SQLDRIVERCONNECT, SQL_API_SQLMORERESULTS, SQL_API_SQLPRIMARYKEYS, + SQL_API_SQLFOREIGNKEYS, + + // ODBC 2.0 APIs + SQL_API_SQLSETSTMTOPTION, SQL_API_SQLGETSTMTOPTION, SQL_API_SQLSETCONNECTOPTION, + SQL_API_SQLGETCONNECTOPTION, SQL_API_SQLALLOCCONNECT, SQL_API_SQLALLOCENV, + SQL_API_SQLALLOCSTMT, SQL_API_SQLFREEENV, SQL_API_SQLFREECONNECT, + + // Driver Manager APIs + SQL_API_SQLGETFUNCTIONS, SQL_API_SQLDRIVERS, SQL_API_SQLDATASOURCES}; + SQLUSMALLINT api_exists; + for (SQLUSMALLINT api : supported_functions) { + ASSERT_EQ(SQL_SUCCESS, SQLGetFunctions(this->conn, api, &api_exists)); + + EXPECT_EQ(SQL_TRUE, api_exists); + + api_exists = -1; + } +} + +TYPED_TEST(GetFunctionsTest, TestSQLGetFunctionsUnsupportedSingleAPI) { + const std::vector unsupported_functions = { + SQL_API_SQLPUTDATA, SQL_API_SQLGETDESCFIELD, SQL_API_SQLGETDESCREC, + SQL_API_SQLCOPYDESC, SQL_API_SQLPARAMDATA, SQL_API_SQLENDTRAN, + SQL_API_SQLSETCURSORNAME, SQL_API_SQLSETDESCFIELD, SQL_API_SQLSETDESCREC, + SQL_API_SQLGETCURSORNAME, SQL_API_SQLSTATISTICS, SQL_API_SQLSPECIALCOLUMNS, + SQL_API_SQLBINDPARAMETER, SQL_API_SQLBROWSECONNECT, SQL_API_SQLNUMPARAMS, + SQL_API_SQLBULKOPERATIONS, SQL_API_SQLCOLUMNPRIVILEGES, SQL_API_SQLPROCEDURECOLUMNS, + SQL_API_SQLDESCRIBEPARAM, SQL_API_SQLPROCEDURES, SQL_API_SQLSETPOS, + SQL_API_SQLTABLEPRIVILEGES}; + SQLUSMALLINT api_exists; + for (SQLUSMALLINT api : unsupported_functions) { + ASSERT_EQ(SQL_SUCCESS, SQLGetFunctions(this->conn, api, &api_exists)); + + EXPECT_EQ(SQL_FALSE, api_exists); + + api_exists = -1; + } +} + +TYPED_TEST(GetFunctionsOdbcV2Test, TestSQLGetFunctionsSupportedSingleAPI) { + const std::vector supported_functions = { + SQL_API_SQLCONNECT, SQL_API_SQLGETINFO, SQL_API_SQLDESCRIBECOL, + SQL_API_SQLGETTYPEINFO, SQL_API_SQLDISCONNECT, SQL_API_SQLNUMRESULTCOLS, + SQL_API_SQLPREPARE, SQL_API_SQLEXECDIRECT, SQL_API_SQLEXECUTE, SQL_API_SQLROWCOUNT, + SQL_API_SQLFETCH, SQL_API_SQLFREESTMT, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS, + SQL_API_SQLTABLES, SQL_API_SQLNATIVESQL, SQL_API_SQLDRIVERCONNECT, + SQL_API_SQLMORERESULTS, SQL_API_SQLSETSTMTOPTION, SQL_API_SQLGETSTMTOPTION, + SQL_API_SQLSETCONNECTOPTION, SQL_API_SQLGETCONNECTOPTION, SQL_API_SQLALLOCCONNECT, + SQL_API_SQLALLOCENV, SQL_API_SQLALLOCSTMT, SQL_API_SQLFREEENV, + SQL_API_SQLFREECONNECT, SQL_API_SQLPRIMARYKEYS, SQL_API_SQLFOREIGNKEYS, + + // Driver Manager APIs + SQL_API_SQLGETFUNCTIONS, SQL_API_SQLDRIVERS, SQL_API_SQLDATASOURCES}; + SQLUSMALLINT api_exists; + for (SQLUSMALLINT api : supported_functions) { + ASSERT_EQ(SQL_SUCCESS, SQLGetFunctions(this->conn, api, &api_exists)); + + EXPECT_EQ(SQL_TRUE, api_exists); + + api_exists = -1; + } +} + +TYPED_TEST(GetFunctionsOdbcV2Test, TestSQLGetFunctionsUnsupportedSingleAPI) { + const std::vector unsupported_functions = { + SQL_API_SQLPUTDATA, SQL_API_SQLPARAMDATA, SQL_API_SQLSETCURSORNAME, + SQL_API_SQLGETCURSORNAME, SQL_API_SQLSTATISTICS, SQL_API_SQLSPECIALCOLUMNS, + SQL_API_SQLBINDPARAMETER, SQL_API_SQLBROWSECONNECT, SQL_API_SQLNUMPARAMS, + SQL_API_SQLBULKOPERATIONS, SQL_API_SQLCOLUMNPRIVILEGES, SQL_API_SQLPROCEDURECOLUMNS, + SQL_API_SQLDESCRIBEPARAM, SQL_API_SQLPROCEDURES, SQL_API_SQLSETPOS, + SQL_API_SQLTABLEPRIVILEGES}; + SQLUSMALLINT api_exists; + for (SQLUSMALLINT api : unsupported_functions) { + ASSERT_EQ(SQL_SUCCESS, SQLGetFunctions(this->conn, api, &api_exists)); + + EXPECT_EQ(SQL_FALSE, api_exists); + + api_exists = -1; + } +} + +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc index 3f12e35c6d6..470a68b3beb 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc @@ -130,9 +130,9 @@ std::wstring ODBCRemoteTestBase::GetQueryAllDataTypes() { CAST(true AS BOOLEAN) AS bit_true, --Character types - 'Z' AS c_char, '你' AS c_wchar, + 'Z' AS c_char, _utf8'你' AS c_wchar, - '你好' AS c_wvarchar, + _utf8'你好' AS c_wvarchar, 'XYZ' AS c_varchar, @@ -245,7 +245,7 @@ std::string ODBCMockTestBase::GetConnectionString() { std::string connect_str( "driver={Apache Arrow Flight SQL ODBC Driver};HOST=localhost;port=" + std::to_string(port) + ";token=" + std::string(kTestToken) + - ";useEncryption=false;"); + ";useEncryption=false;UseWideChar=true;"); return connect_str; } diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h index 7dd77d8fa62..7d24216b9fd 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h @@ -236,7 +236,13 @@ static constexpr std::string_view kErrorStateHY106 = "HY106"; static constexpr std::string_view kErrorStateHY114 = "HY114"; static constexpr std::string_view kErrorStateHY118 = "HY118"; static constexpr std::string_view kErrorStateHYC00 = "HYC00"; +static constexpr std::string_view kErrorStateIM001 = "IM001"; +static constexpr std::string_view kErrorStateS1000 = "S1000"; static constexpr std::string_view kErrorStateS1004 = "S1004"; +static constexpr std::string_view kErrorStateS1002 = "S1002"; +static constexpr std::string_view kErrorStateS1010 = "S1010"; +static constexpr std::string_view kErrorStateS1090 = "S1090"; +static constexpr std::string_view kErrorStateS1C00 = "S1C00"; /// Verify ODBC Error State void VerifyOdbcErrorState(SQLSMALLINT handle_type, SQLHANDLE handle, 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 index 5b6821430a1..ffee80be671 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/statement_attr_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_attr_test.cc @@ -63,6 +63,8 @@ void GetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLPOINTER* value) { SQLGetStmtAttr(statement, attribute, value, SQL_IS_POINTER, &string_length)); } +#if defined(SQL_ATTR_ASYNC_STMT_EVENT) || defined(SQL_ATTR_ASYNC_STMT_PCALLBACK) || \ + defined(SQL_ATTR_ASYNC_STMT_PCONTEXT) // Validate error return value and code void ValidateGetStmtAttrErrorCode(SQLHSTMT statement, SQLINTEGER attribute, std::string_view error_code) { @@ -74,6 +76,8 @@ void ValidateGetStmtAttrErrorCode(SQLHSTMT statement, SQLINTEGER attribute, VerifyOdbcErrorState(SQL_HANDLE_STMT, statement, error_code); } +#endif // SQL_ATTR_ASYNC_STMT_EVENT || SQL_ATTR_ASYNC_STMT_PCALLBACK || + // SQL_ATTR_ASYNC_STMT_PCONTEXT // Validate return value for call to SQLSetStmtAttr with SQLULEN void ValidateSetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLULEN new_value) { @@ -325,8 +329,7 @@ TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowBindType) { EXPECT_EQ(static_cast(0), value); } -TYPED_TEST(StatementAttributeTest, DISABLED_TestSQLGetStmtAttrRowNumber) { - // GH-47711 TODO: enable test after SQLExecDirect support +TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowNumber) { std::wstring wsql = L"SELECT 1;"; std::vector sql0(wsql.begin(), wsql.end()); @@ -384,7 +387,7 @@ TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowsetSize) { EXPECT_EQ(static_cast(1), value); } -TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrAppParamDesc) { +TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrAppParamDescSegFault) { SQLULEN app_param_desc = 0; SQLINTEGER string_length_ptr; @@ -397,7 +400,7 @@ TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrAppParamDesc) { static_cast(app_param_desc)); } -TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrAppRowDesc) { +TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrAppRowDescSegFault) { SQLULEN app_row_desc = 0; SQLINTEGER string_length_ptr; diff --git a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc index 19caba19cac..1eb729a16f9 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc @@ -105,7 +105,11 @@ TYPED_TEST(StatementTest, TestSQLPrepareInvalidQuery) { ASSERT_EQ(SQL_ERROR, SQLExecute(this->stmt)); // Verify function sequence error state is returned +#ifdef __APPLE__ + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateS1010); +#else VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY010); +#endif // __APPLE__ } TYPED_TEST(StatementTest, TestSQLExecDirectDataQuery) { @@ -1407,7 +1411,7 @@ TYPED_TEST(StatementTest, TestSQLBindColDataQuery) { SQLBindCol(this->stmt, 25, SQL_C_CHAR, &char_val, buf_len, &ind)); SQLWCHAR wchar_val[2]; - size_t wchar_size = arrow::flight::sql::odbc::GetSqlWCharSize(); + size_t wchar_size = GetSqlWCharSize(); buf_len = wchar_size * 2; ASSERT_EQ(SQL_SUCCESS, @@ -1437,10 +1441,10 @@ TYPED_TEST(StatementTest, TestSQLBindColDataQuery) { SQL_TIMESTAMP_STRUCT timestamp_val_min{}, timestamp_val_max{}; - EXPECT_EQ(SQL_SUCCESS, SQLBindCol(this->stmt, 31, SQL_C_TYPE_TIMESTAMP, + ASSERT_EQ(SQL_SUCCESS, SQLBindCol(this->stmt, 31, SQL_C_TYPE_TIMESTAMP, ×tamp_val_min, buf_len, &ind)); - EXPECT_EQ(SQL_SUCCESS, SQLBindCol(this->stmt, 32, SQL_C_TYPE_TIMESTAMP, + ASSERT_EQ(SQL_SUCCESS, SQLBindCol(this->stmt, 32, SQL_C_TYPE_TIMESTAMP, ×tamp_val_max, buf_len, &ind)); // Execute query and fetch data once since there is only 1 row. @@ -1816,8 +1820,86 @@ TYPED_TEST(StatementTest, TestSQLBindColIndicatorOnlySQLUnbind) { // EXPECT_EQ(1, char_val_ind); } +TYPED_TEST(StatementTest, TestSQLExtendedFetchRowFetching) { + // Set SQL_ROWSET_SIZE to fetch 3 rows at once + + 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. + ASSERT_EQ(SQL_SUCCESS, SQLBindCol(this->stmt, 1, SQL_C_LONG, val, buf_len, ind)); + + ASSERT_EQ(SQL_SUCCESS, 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()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + // Fetch row 1-3. + SQLULEN row_count; + SQLUSMALLINT row_status[rows]; + + ASSERT_EQ(SQL_SUCCESS, + SQLExtendedFetch(this->stmt, SQL_FETCH_NEXT, 0, &row_count, row_status)); + EXPECT_EQ(3, row_count); + + for (int i = 0; i < rows; i++) { + EXPECT_EQ(SQL_SUCCESS, row_status[i]); + } + + // Verify 1 is returned for row 1 + EXPECT_EQ(1, val[0]); + // Verify 2 is returned for row 2 + EXPECT_EQ(2, val[1]); + // Verify 3 is returned for row 3 + EXPECT_EQ(3, val[2]); + + // Verify result set has no more data beyond row 3 + SQLULEN row_count2; + SQLUSMALLINT row_status2[rows]; + EXPECT_EQ(SQL_NO_DATA, + SQLExtendedFetch(this->stmt, SQL_FETCH_NEXT, 0, &row_count2, row_status2)); +} + +TEST_F(StatementRemoteTest, DISABLED_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. + SQLINTEGER val; + + ASSERT_EQ(SQL_SUCCESS, 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()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + SQLULEN row_count1; + SQLUSMALLINT row_status1[1]; + + // SQLExtendedFetch should return SQL_SUCCESS_WITH_INFO for 22002 state + ASSERT_EQ(SQL_SUCCESS_WITH_INFO, + SQLExtendedFetch(this->stmt, SQL_FETCH_NEXT, 0, &row_count1, row_status1)); + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState22002); +} + TYPED_TEST(StatementTest, TestSQLMoreResultsNoData) { - // Verify SQLMoreResults returns SQL_NO_DATA by default. + // Verify SQLMoreResults is stubbed to return SQL_NO_DATA + std::wstring wsql = L"SELECT 1;"; std::vector sql0(wsql.begin(), wsql.end()); @@ -1934,7 +2016,11 @@ TYPED_TEST(StatementTest, TestSQLNativeSqlReturnsErrorOnBadInputs) { ASSERT_EQ(SQL_ERROR, SQLNativeSql(this->conn, input_str, -100, buf, buf_char_len, &output_char_len)); +#ifdef __APPLE__ + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateS1090); +#else VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY090); +#endif // __APPLE__ } TYPED_TEST(StatementTest, SQLNumResultColsReturnsColumnsOnSelect) { @@ -1978,7 +2064,14 @@ TYPED_TEST(StatementTest, SQLNumResultColsFunctionSequenceErrorOnNoQuery) { ASSERT_EQ(SQL_ERROR, SQLNumResultCols(this->stmt, &column_count)); VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY010); - EXPECT_EQ(expected_value, column_count); + ASSERT_EQ(SQL_ERROR, SQLNumResultCols(this->stmt, &column_count)); +#ifdef __APPLE__ + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateS1010); +#else + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY010); +#endif // __APPLE__ + + ASSERT_EQ(expected_value, column_count); } TYPED_TEST(StatementTest, SQLRowCountReturnsNegativeOneOnSelect) { @@ -2020,11 +2113,25 @@ TYPED_TEST(StatementTest, SQLRowCountFunctionSequenceErrorOnNoQuery) { SQLLEN expected_value = 0; ASSERT_EQ(SQL_ERROR, SQLRowCount(this->stmt, &row_count)); +#ifdef __APPLE__ + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateS1010); +#else VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY010); +#endif // __APPLE__ EXPECT_EQ(expected_value, row_count); } +TYPED_TEST(StatementTest, TestSQLFreeStmtSQLClose) { + std::wstring wsql = L"SELECT 1;"; + std::vector sql0(wsql.begin(), wsql.end()); + + ASSERT_EQ(SQL_SUCCESS, + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size()))); + + ASSERT_EQ(SQL_SUCCESS, SQLFreeStmt(this->stmt, SQL_CLOSE)); +} + TYPED_TEST(StatementTest, TestSQLCloseCursor) { std::wstring wsql = L"SELECT 1;"; std::vector sql0(wsql.begin(), wsql.end()); @@ -2036,7 +2143,7 @@ TYPED_TEST(StatementTest, TestSQLCloseCursor) { } TYPED_TEST(StatementTest, TestSQLFreeStmtSQLCloseWithoutCursor) { - // Verify SQLFreeStmt(SQL_CLOSE) does not throw error with invalid cursor + // SQLFreeStmt(SQL_CLOSE) does not throw error with invalid cursor ASSERT_EQ(SQL_SUCCESS, SQLFreeStmt(this->stmt, SQL_CLOSE)); } diff --git a/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc index 01a19337f4f..c09b21b06dd 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc @@ -487,4 +487,97 @@ TEST_F(TablesRemoteTest, SQLTablesGetSupportedTableTypes) { ValidateFetch(this->stmt, SQL_NO_DATA); } +TYPED_TEST(TablesTest, SQLTablesGetMetadataBySQLDescribeCol) { + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 0; + + const SQLWCHAR* column_names[] = {static_cast(L"TABLE_CAT"), + static_cast(L"TABLE_SCHEM"), + static_cast(L"TABLE_NAME"), + static_cast(L"TABLE_TYPE"), + static_cast(L"REMARKS")}; + SQLSMALLINT column_data_types[] = {SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_WVARCHAR}; + SQLULEN column_sizes[] = {1024, 1024, 1024, 1024, 1024}; + + ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, + nullptr, SQL_NTS, nullptr, SQL_NTS)); + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + column_index = i + 1; + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(wcslen(column_names[i]), name_length); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(column_names[i], returned); + EXPECT_EQ(column_data_types[i], column_data_type); + EXPECT_EQ(column_sizes[i], column_size); + EXPECT_EQ(0, decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); + + name_length = 0; + column_data_type = 0; + column_size = 0; + decimal_digits = 0; + nullable = 0; + } +} + +TYPED_TEST(TablesOdbcV2Test, SQLTablesGetMetadataBySQLDescribeColODBC2) { + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + size_t column_index = 0; + + const SQLWCHAR* column_names[] = {static_cast(L"TABLE_QUALIFIER"), + static_cast(L"TABLE_OWNER"), + static_cast(L"TABLE_NAME"), + static_cast(L"TABLE_TYPE"), + static_cast(L"REMARKS")}; + SQLSMALLINT column_data_types[] = {SQL_WVARCHAR, SQL_WVARCHAR, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_WVARCHAR}; + SQLULEN column_sizes[] = {1024, 1024, 1024, 1024, 1024}; + + ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, + nullptr, SQL_NTS, nullptr, SQL_NTS)); + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + column_index = i + 1; + + ASSERT_EQ(SQL_SUCCESS, SQLDescribeCol(this->stmt, column_index, column_name, + buf_char_len, &name_length, &column_data_type, + &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(wcslen(column_names[i]), name_length); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(column_names[i], returned); + EXPECT_EQ(column_data_types[i], column_data_type); + EXPECT_EQ(column_sizes[i], column_size); + EXPECT_EQ(0, decimal_digits); + EXPECT_EQ(SQL_NULLABLE, nullable); + + name_length = 0; + column_data_type = 0; + column_size = 0; + decimal_digits = 0; + nullable = 0; + } +} } // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/type_info_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/type_info_test.cc new file mode 100644 index 00000000000..838aaed2447 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/type_info_test.cc @@ -0,0 +1,1897 @@ +// 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/odbc_impl/platform.h" + +#include +#include +#include + +#include + +namespace arrow::flight::sql::odbc { + +using std::optional; + +template +class TypeInfoTest : public T {}; + +class TypeInfoMockTest : public FlightSQLODBCMockTestBase {}; +using TestTypes = ::testing::Types; +TYPED_TEST_SUITE(TypeInfoTest, TestTypes); + +class TypeInfoOdbcV2MockTest : public FlightSQLOdbcV2MockTestBase {}; + +namespace { +// Helper Functions + +void CheckSQLDescribeCol(SQLHSTMT stmt, const SQLUSMALLINT column_index, + const std::wstring& expected_name, + const SQLSMALLINT& expected_data_type, + const SQLULEN& expected_column_size, + const SQLSMALLINT& expected_decimal_digits, + const SQLSMALLINT& expected_nullable) { + SQLWCHAR column_name[1024]; + SQLSMALLINT buf_char_len = + static_cast(sizeof(column_name) / GetSqlWCharSize()); + SQLSMALLINT name_length = 0; + SQLSMALLINT column_data_type = 0; + SQLULEN column_size = 0; + SQLSMALLINT decimal_digits = 0; + SQLSMALLINT nullable = 0; + + ASSERT_EQ(SQL_SUCCESS, + SQLDescribeCol(stmt, column_index, column_name, buf_char_len, &name_length, + &column_data_type, &column_size, &decimal_digits, &nullable)); + + EXPECT_EQ(name_length, expected_name.size()); + + std::wstring returned(column_name, column_name + name_length); + EXPECT_EQ(expected_name, returned); + EXPECT_EQ(expected_data_type, column_data_type); + EXPECT_EQ(expected_column_size, column_size); + EXPECT_EQ(expected_decimal_digits, decimal_digits); + EXPECT_EQ(expected_nullable, nullable); +} + +void CheckSQLDescribeColODBC2(SQLHSTMT stmt) { + const SQLWCHAR* column_names[] = {static_cast(L"TYPE_NAME"), + static_cast(L"DATA_TYPE"), + static_cast(L"PRECISION"), + static_cast(L"LITERAL_PREFIX"), + static_cast(L"LITERAL_SUFFIX"), + static_cast(L"CREATE_PARAMS"), + static_cast(L"NULLABLE"), + static_cast(L"CASE_SENSITIVE"), + static_cast(L"SEARCHABLE"), + static_cast(L"UNSIGNED_ATTRIBUTE"), + static_cast(L"MONEY"), + static_cast(L"AUTO_INCREMENT"), + static_cast(L"LOCAL_TYPE_NAME"), + static_cast(L"MINIMUM_SCALE"), + static_cast(L"MAXIMUM_SCALE"), + static_cast(L"SQL_DATA_TYPE"), + static_cast(L"SQL_DATETIME_SUB"), + static_cast(L"NUM_PREC_RADIX"), + static_cast(L"INTERVAL_PRECISION")}; + SQLSMALLINT column_data_types[] = { + SQL_WVARCHAR, SQL_SMALLINT, SQL_INTEGER, SQL_WVARCHAR, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_SMALLINT, SQL_SMALLINT, SQL_SMALLINT, SQL_SMALLINT, + SQL_SMALLINT, SQL_SMALLINT, SQL_WVARCHAR, SQL_SMALLINT, SQL_SMALLINT, + SQL_SMALLINT, SQL_SMALLINT, SQL_INTEGER, SQL_SMALLINT}; + SQLULEN column_sizes[] = {1024, 2, 4, 1024, 1024, 1024, 2, 2, 2, 2, + 2, 2, 1024, 2, 2, 2, 2, 4, 2}; + SQLSMALLINT column_decimal_digits[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + SQLSMALLINT column_nullable[] = {SQL_NO_NULLS, SQL_NO_NULLS, SQL_NULLABLE, SQL_NULLABLE, + SQL_NULLABLE, SQL_NULLABLE, SQL_NO_NULLS, SQL_NO_NULLS, + SQL_NO_NULLS, SQL_NULLABLE, SQL_NO_NULLS, SQL_NULLABLE, + SQL_NULLABLE, SQL_NULLABLE, SQL_NULLABLE, SQL_NO_NULLS, + SQL_NULLABLE, SQL_NULLABLE, SQL_NULLABLE}; + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + SQLUSMALLINT column_index = i + 1; + CheckSQLDescribeCol(stmt, column_index, column_names[i], column_data_types[i], + column_sizes[i], column_decimal_digits[i], column_nullable[i]); + } +} + +void CheckSQLDescribeColODBC3(SQLHSTMT stmt) { + const SQLWCHAR* column_names[] = {static_cast(L"TYPE_NAME"), + static_cast(L"DATA_TYPE"), + static_cast(L"COLUMN_SIZE"), + static_cast(L"LITERAL_PREFIX"), + static_cast(L"LITERAL_SUFFIX"), + static_cast(L"CREATE_PARAMS"), + static_cast(L"NULLABLE"), + static_cast(L"CASE_SENSITIVE"), + static_cast(L"SEARCHABLE"), + static_cast(L"UNSIGNED_ATTRIBUTE"), + static_cast(L"FIXED_PREC_SCALE"), + static_cast(L"AUTO_UNIQUE_VALUE"), + static_cast(L"LOCAL_TYPE_NAME"), + static_cast(L"MINIMUM_SCALE"), + static_cast(L"MAXIMUM_SCALE"), + static_cast(L"SQL_DATA_TYPE"), + static_cast(L"SQL_DATETIME_SUB"), + static_cast(L"NUM_PREC_RADIX"), + static_cast(L"INTERVAL_PRECISION")}; + SQLSMALLINT column_data_types[] = { + SQL_WVARCHAR, SQL_SMALLINT, SQL_INTEGER, SQL_WVARCHAR, SQL_WVARCHAR, + SQL_WVARCHAR, SQL_SMALLINT, SQL_SMALLINT, SQL_SMALLINT, SQL_SMALLINT, + SQL_SMALLINT, SQL_SMALLINT, SQL_WVARCHAR, SQL_SMALLINT, SQL_SMALLINT, + SQL_SMALLINT, SQL_SMALLINT, SQL_INTEGER, SQL_SMALLINT}; + SQLULEN column_sizes[] = {1024, 2, 4, 1024, 1024, 1024, 2, 2, 2, 2, + 2, 2, 1024, 2, 2, 2, 2, 4, 2}; + SQLSMALLINT column_decimal_digits[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + SQLSMALLINT column_nullable[] = {SQL_NO_NULLS, SQL_NO_NULLS, SQL_NULLABLE, SQL_NULLABLE, + SQL_NULLABLE, SQL_NULLABLE, SQL_NO_NULLS, SQL_NO_NULLS, + SQL_NO_NULLS, SQL_NULLABLE, SQL_NO_NULLS, SQL_NULLABLE, + SQL_NULLABLE, SQL_NULLABLE, SQL_NULLABLE, SQL_NO_NULLS, + SQL_NULLABLE, SQL_NULLABLE, SQL_NULLABLE}; + + for (size_t i = 0; i < sizeof(column_names) / sizeof(*column_names); ++i) { + SQLUSMALLINT column_index = i + 1; + CheckSQLDescribeCol(stmt, column_index, column_names[i], column_data_types[i], + column_sizes[i], column_decimal_digits[i], column_nullable[i]); + } +} + +void CheckSQLGetTypeInfo( + SQLHSTMT stmt, const std::wstring& expected_type_name, + const SQLSMALLINT& expected_data_type, const SQLINTEGER& expected_column_size, + const optional& expected_literal_prefix, + const optional& expected_literal_suffix, + const optional& expected_create_params, + const SQLSMALLINT& expected_nullable, const SQLSMALLINT& expected_case_sensitive, + const SQLSMALLINT& expected_searchable, const SQLSMALLINT& expected_unsigned_attr, + const SQLSMALLINT& expected_fixed_prec_scale, + const SQLSMALLINT& expected_auto_unique_value, + const std::wstring& expected_local_type_name, const SQLSMALLINT& expected_min_scale, + const SQLSMALLINT& expected_max_scale, const SQLSMALLINT& expected_sql_data_type, + const SQLSMALLINT& expected_sql_datetime_sub, + const SQLINTEGER& expected_num_prec_radix, const SQLINTEGER& expected_interval_prec) { + CheckStringColumnW(stmt, 1, expected_type_name); // type name + CheckSmallIntColumn(stmt, 2, expected_data_type); // data type + CheckIntColumn(stmt, 3, expected_column_size); // column size + + if (expected_literal_prefix) { // literal prefix + CheckStringColumnW(stmt, 4, *expected_literal_prefix); + } else { + CheckNullColumnW(stmt, 4); + } + + if (expected_literal_suffix) { // literal suffix + CheckStringColumnW(stmt, 5, *expected_literal_suffix); + } else { + CheckNullColumnW(stmt, 5); + } + + if (expected_create_params) { // create params + CheckStringColumnW(stmt, 6, *expected_create_params); + } else { + CheckNullColumnW(stmt, 6); + } + + CheckSmallIntColumn(stmt, 7, expected_nullable); // nullable + CheckSmallIntColumn(stmt, 8, expected_case_sensitive); // case sensitive + CheckSmallIntColumn(stmt, 9, expected_searchable); // searchable + CheckSmallIntColumn(stmt, 10, expected_unsigned_attr); // unsigned attr + CheckSmallIntColumn(stmt, 11, expected_fixed_prec_scale); // fixed prec scale + CheckSmallIntColumn(stmt, 12, expected_auto_unique_value); // auto unique value + CheckStringColumnW(stmt, 13, expected_local_type_name); // local type name + CheckSmallIntColumn(stmt, 14, expected_min_scale); // min scale + CheckSmallIntColumn(stmt, 15, expected_max_scale); // max scale + CheckSmallIntColumn(stmt, 16, expected_sql_data_type); // sql data type + CheckSmallIntColumn(stmt, 17, expected_sql_datetime_sub); // sql datetime sub + CheckIntColumn(stmt, 18, expected_num_prec_radix); // num prec radix + CheckIntColumn(stmt, 19, expected_interval_prec); // interval prec +} +} // namespace + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoAllTypes) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_ALL_TYPES)); + + // Check bit data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"bit"), // expected_type_name + SQL_BIT, // expected_data_type + 1, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"bit"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_BIT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check tinyint data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"tinyint"), // expected_type_name + SQL_TINYINT, // expected_data_type + 3, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"tinyint"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_TINYINT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check bigint data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"bigint"), // expected_type_name + SQL_BIGINT, // expected_data_type + 19, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"bigint"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_BIGINT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check longvarbinary data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"longvarbinary"), // expected_type_name + SQL_LONGVARBINARY, // expected_data_type + 65536, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"longvarbinary"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_LONGVARBINARY, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check varbinary data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"varbinary"), // expected_type_name + SQL_VARBINARY, // expected_data_type + 255, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"varbinary"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_VARBINARY, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check text data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Driver returns SQL_WLONGVARCHAR since unicode is enabled + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"text"), // expected_type_name + SQL_WLONGVARCHAR, // expected_data_type + 65536, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"text"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WLONGVARCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check longvarchar data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"longvarchar"), // expected_type_name + SQL_WLONGVARCHAR, // expected_data_type + 65536, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"longvarchar"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WLONGVARCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check char data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Driver returns SQL_WCHAR since unicode is enabled + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"char"), // expected_type_name + SQL_WCHAR, // expected_data_type + 255, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"char"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check integer data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"integer"), // expected_type_name + SQL_INTEGER, // expected_data_type + 9, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"integer"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_INTEGER, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check smallint data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"smallint"), // expected_type_name + SQL_SMALLINT, // expected_data_type + 5, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"smallint"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_SMALLINT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check float data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"float"), // expected_type_name + SQL_FLOAT, // expected_data_type + 7, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"float"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_FLOAT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check double data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"double"), // expected_type_name + SQL_DOUBLE, // expected_data_type + 15, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"double"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DOUBLE, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check numeric data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Mock server treats numeric data type as a double type + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"numeric"), // expected_type_name + SQL_DOUBLE, // expected_data_type + 15, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"numeric"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DOUBLE, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check varchar data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Driver returns SQL_WVARCHAR since unicode is enabled + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"varchar"), // expected_type_name + SQL_WVARCHAR, // expected_data_type + 255, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"varchar"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WVARCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check date data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"date"), // expected_type_name + SQL_TYPE_DATE, // expected_data_type + 10, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"date"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + SQL_CODE_DATE, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check time data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"time"), // expected_type_name + SQL_TYPE_TIME, // expected_data_type + 8, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"time"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + SQL_CODE_TIME, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check timestamp data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"timestamp"), // expected_type_name + SQL_TYPE_TIMESTAMP, // expected_data_type + 32, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"timestamp"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + SQL_CODE_TIMESTAMP, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); +} + +TEST_F(TypeInfoOdbcV2MockTest, TestSQLGetTypeInfoAllTypes) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_ALL_TYPES)); + + // Check bit data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"bit"), // expected_type_name + SQL_BIT, // expected_data_type + 1, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"bit"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_BIT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check tinyint data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"tinyint"), // expected_type_name + SQL_TINYINT, // expected_data_type + 3, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"tinyint"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_TINYINT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check bigint data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"bigint"), // expected_type_name + SQL_BIGINT, // expected_data_type + 19, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"bigint"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_BIGINT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check longvarbinary data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"longvarbinary"), // expected_type_name + SQL_LONGVARBINARY, // expected_data_type + 65536, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"longvarbinary"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_LONGVARBINARY, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check varbinary data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"varbinary"), // expected_type_name + SQL_VARBINARY, // expected_data_type + 255, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"varbinary"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_VARBINARY, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check text data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Driver returns SQL_WLONGVARCHAR since unicode is enabled + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"text"), // expected_type_name + SQL_WLONGVARCHAR, // expected_data_type + 65536, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"text"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WLONGVARCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check longvarchar data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"longvarchar"), // expected_type_name + SQL_WLONGVARCHAR, // expected_data_type + 65536, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"longvarchar"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WLONGVARCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check char data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Driver returns SQL_WCHAR since unicode is enabled + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"char"), // expected_type_name + SQL_WCHAR, // expected_data_type + 255, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"char"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check integer data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"integer"), // expected_type_name + SQL_INTEGER, // expected_data_type + 9, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"integer"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_INTEGER, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check smallint data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"smallint"), // expected_type_name + SQL_SMALLINT, // expected_data_type + 5, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"smallint"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_SMALLINT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check float data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"float"), // expected_type_name + SQL_FLOAT, // expected_data_type + 7, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"float"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_FLOAT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check double data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"double"), // expected_type_name + SQL_DOUBLE, // expected_data_type + 15, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"double"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DOUBLE, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check numeric data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Mock server treats numeric data type as a double type + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"numeric"), // expected_type_name + SQL_DOUBLE, // expected_data_type + 15, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"numeric"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DOUBLE, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check varchar data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Driver returns SQL_WVARCHAR since unicode is enabled + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"varchar"), // expected_type_name + SQL_WVARCHAR, // expected_data_type + 255, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"varchar"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WVARCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check date data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"date"), // expected_type_name + SQL_DATE, // expected_data_type + 10, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"date"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + NULL, // expected_sql_datetime_sub, driver returns NULL for Ver2 + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check time data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"time"), // expected_type_name + SQL_TIME, // expected_data_type + 8, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"time"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + NULL, // expected_sql_datetime_sub, driver returns NULL for Ver2 + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // Check timestamp data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"timestamp"), // expected_type_name + SQL_TIMESTAMP, // expected_data_type + 32, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"timestamp"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + NULL, // expected_sql_datetime_sub, driver returns NULL for Ver2 + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoBit) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_BIT)); + + // Check bit data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"bit"), // expected_type_name + SQL_BIT, // expected_data_type + 1, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"bit"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_BIT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoTinyInt) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_TINYINT)); + + // Check tinyint data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"tinyint"), // expected_type_name + SQL_TINYINT, // expected_data_type + 3, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"tinyint"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_TINYINT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoBigInt) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_BIGINT)); + + // Check bigint data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"bigint"), // expected_type_name + SQL_BIGINT, // expected_data_type + 19, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"bigint"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_BIGINT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoLongVarbinary) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_LONGVARBINARY)); + + // Check longvarbinary data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"longvarbinary"), // expected_type_name + SQL_LONGVARBINARY, // expected_data_type + 65536, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"longvarbinary"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_LONGVARBINARY, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoVarbinary) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_VARBINARY)); + + // Check varbinary data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"varbinary"), // expected_type_name + SQL_VARBINARY, // expected_data_type + 255, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"varbinary"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_VARBINARY, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoLongVarchar) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_WLONGVARCHAR)); + + // Check text data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Driver returns SQL_WLONGVARCHAR since unicode is enabled + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"text"), // expected_type_name + SQL_WLONGVARCHAR, // expected_data_type + 65536, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"text"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WLONGVARCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check longvarchar data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"longvarchar"), // expected_type_name + SQL_WLONGVARCHAR, // expected_data_type + 65536, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"longvarchar"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WLONGVARCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoChar) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_WCHAR)); + + // Check char data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Driver returns SQL_WCHAR since unicode is enabled + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"char"), // expected_type_name + SQL_WCHAR, // expected_data_type + 255, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + NULL, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"char"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoInteger) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_INTEGER)); + + // Check integer data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"integer"), // expected_type_name + SQL_INTEGER, // expected_data_type + 9, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"integer"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_INTEGER, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoSmallInt) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_SMALLINT)); + + // Check smallint data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"smallint"), // expected_type_name + SQL_SMALLINT, // expected_data_type + 5, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"smallint"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_SMALLINT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoFloat) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_FLOAT)); + + // Check float data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"float"), // expected_type_name + SQL_FLOAT, // expected_data_type + 7, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"float"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_FLOAT, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoDouble) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_DOUBLE)); + + // Check double data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"double"), // expected_type_name + SQL_DOUBLE, // expected_data_type + 15, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"double"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DOUBLE, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // Check numeric data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Mock server treats numeric data type as a double type + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"numeric"), // expected_type_name + SQL_DOUBLE, // expected_data_type + 15, // expected_column_size + std::nullopt, // expected_literal_prefix + std::nullopt, // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"numeric"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DOUBLE, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoVarchar) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_WVARCHAR)); + + // Check varchar data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + // Driver returns SQL_WVARCHAR since unicode is enabled + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"varchar"), // expected_type_name + SQL_WVARCHAR, // expected_data_type + 255, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::wstring(L"length"), // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"varchar"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_WVARCHAR, // expected_sql_data_type + NULL, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoSQLTypeDate) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_TYPE_DATE)); + + // Check date data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"date"), // expected_type_name + SQL_TYPE_DATE, // expected_data_type + 10, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"date"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + SQL_CODE_DATE, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoSQLDate) { + // Pass ODBC Ver 2 data type + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_DATE)); + + // Check date data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"date"), // expected_type_name + SQL_TYPE_DATE, // expected_data_type + 10, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"date"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + SQL_CODE_DATE, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoOdbcV2MockTest, TestSQLGetTypeInfoDate) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_DATE)); + + // Check date data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"date"), // expected_type_name + SQL_DATE, // expected_data_type + 10, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"date"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + NULL, // expected_sql_datetime_sub, driver returns NULL for Ver2 + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoOdbcV2MockTest, TestSQLGetTypeInfoSQLTypeDate) { + // Pass ODBC Ver 3 data type + ASSERT_EQ(SQL_ERROR, SQLGetTypeInfo(this->stmt, SQL_TYPE_DATE)); + + // Driver manager returns SQL data type out of range error state + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateS1004); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoSQLTypeTime) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_TYPE_TIME)); + + // Check time data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"time"), // expected_type_name + SQL_TYPE_TIME, // expected_data_type + 8, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"time"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + SQL_CODE_TIME, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoSQLTime) { + // Pass ODBC Ver 2 data type + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_TIME)); + + // Check time data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"time"), // expected_type_name + SQL_TYPE_TIME, // expected_data_type + 8, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"time"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + SQL_CODE_TIME, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoOdbcV2MockTest, TestSQLGetTypeInfoTime) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_TIME)); + + // Check time data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"time"), // expected_type_name + SQL_TIME, // expected_data_type + 8, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"time"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + NULL, // expected_sql_datetime_sub, driver returns NULL for Ver2 + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoOdbcV2MockTest, TestSQLGetTypeInfoSQLTypeTime) { + // Pass ODBC Ver 3 data type + ASSERT_EQ(SQL_ERROR, SQLGetTypeInfo(this->stmt, SQL_TYPE_TIME)); + + // Driver manager returns SQL data type out of range error state + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateS1004); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoSQLTypeTimestamp) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_TYPE_TIMESTAMP)); + + // Check timestamp data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"timestamp"), // expected_type_name + SQL_TYPE_TIMESTAMP, // expected_data_type + 32, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"timestamp"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + SQL_CODE_TIMESTAMP, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoSQLTimestamp) { + // Pass ODBC Ver 2 data type + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_TIMESTAMP)); + + // Check timestamp data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"timestamp"), // expected_type_name + SQL_TYPE_TIMESTAMP, // expected_data_type + 32, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"timestamp"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + SQL_CODE_TIMESTAMP, // expected_sql_datetime_sub + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC3(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoOdbcV2MockTest, TestSQLGetTypeInfoSQLTimestamp) { + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_TIMESTAMP)); + + // Check timestamp data type + ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt)); + + CheckSQLGetTypeInfo(this->stmt, + std::wstring(L"timestamp"), // expected_type_name + SQL_TIMESTAMP, // expected_data_type + 32, // expected_column_size + std::wstring(L"'"), // expected_literal_prefix + std::wstring(L"'"), // expected_literal_suffix + std::nullopt, // expected_create_params + SQL_NULLABLE, // expected_nullable + SQL_FALSE, // expected_case_sensitive + SQL_SEARCHABLE, // expected_searchable + SQL_FALSE, // expected_unsigned_attr + SQL_FALSE, // expected_fixed_prec_scale + NULL, // expected_auto_unique_value + std::wstring(L"timestamp"), // expected_local_type_name + NULL, // expected_min_scale + NULL, // expected_max_scale + SQL_DATETIME, // expected_sql_data_type + NULL, // expected_sql_datetime_sub, driver returns NULL for Ver2 + NULL, // expected_num_prec_radix + NULL); // expected_interval_prec + + CheckSQLDescribeColODBC2(this->stmt); + + // No more data + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +TEST_F(TypeInfoOdbcV2MockTest, TestSQLGetTypeInfoSQLTypeTimestamp) { + // Pass ODBC Ver 3 data type + ASSERT_EQ(SQL_ERROR, SQLGetTypeInfo(this->stmt, SQL_TYPE_TIMESTAMP)); + + // Driver manager returns SQL data type out of range error state + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateS1004); +} + +TEST_F(TypeInfoMockTest, TestSQLGetTypeInfoInvalidDataType) { + SQLSMALLINT invalid_data_type = -114; + ASSERT_EQ(SQL_ERROR, SQLGetTypeInfo(this->stmt, invalid_data_type)); + VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY004); +} + +TYPED_TEST(TypeInfoTest, TestSQLGetTypeInfoUnsupportedDataType) { + // Assumes mock and remote server don't support GUID data type + + ASSERT_EQ(SQL_SUCCESS, SQLGetTypeInfo(this->stmt, SQL_GUID)); + + // Result set is empty with valid data type that is unsupported by the server + ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt)); +} + +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/vendored/whereami/whereami.cc b/cpp/src/arrow/vendored/whereami/whereami.cc index 945226193f9..94437361ec0 100644 --- a/cpp/src/arrow/vendored/whereami/whereami.cc +++ b/cpp/src/arrow/vendored/whereami/whereami.cc @@ -159,7 +159,7 @@ WAI_NOINLINE WAI_FUNCSPEC int WAI_PREFIX(getModulePath)(char* out, int capacity, return length; } -#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || \ +#elif defined(__APPLE__) || defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || \ defined(WAI_USE_PROC_SELF_EXE) # include diff --git a/cpp/vcpkg.json b/cpp/vcpkg.json index 41c40fcc85f..7e03a515a8f 100644 --- a/cpp/vcpkg.json +++ b/cpp/vcpkg.json @@ -21,10 +21,8 @@ "boost-filesystem", "boost-locale", "boost-multiprecision", - "boost-optional", "boost-process", "boost-system", - "boost-variant", "boost-xpressive", "brotli", "bzip2", From 4ef8ff37583f05e177a6197378315e755c2a91c3 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Mon, 22 Dec 2025 14:37:49 -0800 Subject: [PATCH 2/2] Remove changes added from rebase --- cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h | 14 -------------- .../arrow/flight/sql/odbc/odbc_impl/util_test.cc | 15 ++++----------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h index 11aa2f9913e..e9a6794f104 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h @@ -29,20 +29,6 @@ #include "arrow/flight/types.h" #include "arrow/util/utf8.h" -#define CONVERT_WIDE_STR(wstring_var, utf8_target) \ - wstring_var = [&] { \ - arrow::Result res = arrow::util::UTF8ToWideString(utf8_target); \ - arrow::flight::sql::odbc::util::ThrowIfNotOK(res.status()); \ - return res.ValueOrDie(); \ - }() - -#define CONVERT_UTF8_STR(string_var, wide_str_target) \ - string_var = [&] { \ - arrow::Result res = arrow::util::WideStringToUTF8(wide_str_target); \ - arrow::flight::sql::odbc::util::ThrowIfNotOK(res.status()); \ - return res.ValueOrDie(); \ - }() - #define CONVERT_WIDE_STR(wstring_var, utf8_target) \ wstring_var = [&] { \ arrow::Result res = arrow::util::UTF8ToWideString(utf8_target); \ diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util_test.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util_test.cc index 4946355ff20..c5f4ac5c2c4 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util_test.cc @@ -31,13 +31,6 @@ namespace arrow::flight::sql::odbc { using util::ConvertSqlPatternToRegexString; using util::ConvertToDBMSVer; -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()); } -}; - // A global test "environment", to ensure Arrow compute kernel functions are registered class ComputeKernelEnvironment : public ::testing::Environment { @@ -95,7 +88,7 @@ void TestTime64ArrayConversion(const std::vector& input, AssertConvertedArray(expected_array, converted_array, input.size(), arrow_type); } -TEST_F(UtilTestsWithCompute, Time32ToTimeStampArray) { +TEST(Utils, Time32ToTimeStampArray) { std::vector input_data = {14896, 17820}; const auto seconds_from_epoch = GetTodayTimeFromEpoch(); @@ -114,7 +107,7 @@ TEST_F(UtilTestsWithCompute, Time32ToTimeStampArray) { TestTime32ArrayConversion(input_data, expected, CDataType_TIMESTAMP, Type::TIMESTAMP); } -TEST_F(UtilTestsWithCompute, Time64ToTimeStampArray) { +TEST(Utils, Time64ToTimeStampArray) { std::vector input_data = {1579489200000, 1646881200000}; const auto seconds_from_epoch = GetTodayTimeFromEpoch(); @@ -133,7 +126,7 @@ TEST_F(UtilTestsWithCompute, Time64ToTimeStampArray) { TestTime64ArrayConversion(input_data, expected, CDataType_TIMESTAMP, Type::TIMESTAMP); } -TEST_F(UtilTestsWithCompute, StringToDateArray) { +TEST(Utils, StringToDateArray) { std::shared_ptr expected; ArrayFromVector({1579489200000, 1646881200000}, &expected); @@ -141,7 +134,7 @@ TEST_F(UtilTestsWithCompute, StringToDateArray) { Type::DATE64); } -TEST_F(UtilTestsWithCompute, StringToTimeArray) { +TEST(Utils, StringToTimeArray) { std::shared_ptr expected; ArrayFromVector(time64(TimeUnit::MICRO), {36000000000, 43200000000}, &expected);