diff --git a/.github/workflows/cpp_extra.yml b/.github/workflows/cpp_extra.yml index 5aaad7f3933..2a361be1ede 100644 --- a/.github/workflows/cpp_extra.yml +++ b/.github/workflows/cpp_extra.yml @@ -351,7 +351,7 @@ jobs: odbc-linux: needs: check-labels - name: ODBC Linux + name: ODBC ${{ matrix.build-type }} ${{ matrix.title }} runs-on: ubuntu-latest if: >- needs.check-labels.outputs.force == 'true' || @@ -360,6 +360,15 @@ jobs: timeout-minutes: 75 strategy: fail-fast: false + matrix: + include: + - image: ubuntu-cpp-odbc + title: AMD64 Ubuntu RPM + build-type: release + format: rpm + run-options: >- + -e ARROW_FLIGHT_SQL_ODBC_INSTALLER=ON + -e ODBC_PACKAGE_FORMAT=RPM env: ARCH: amd64 ARCHERY_DEBUG: 1 @@ -377,8 +386,8 @@ jobs: uses: actions/cache@v5 with: path: .docker - key: ubuntu-cpp-odbc-${{ hashFiles('cpp/**') }} - restore-keys: ubuntu-cpp-odbc- + key: ${{ matrix.image }}-${{ matrix.format }}-${{ hashFiles('cpp/**') }} + restore-keys: ${{ matrix.image }}-${{ matrix.format }}- - name: Setup Python on hosted runner uses: actions/setup-python@v6 with: @@ -393,7 +402,14 @@ jobs: # GH-40558: reduce ASLR to avoid ASAN/LSAN crashes sudo sysctl -w vm.mmap_rnd_bits=28 source ci/scripts/util_enable_core_dumps.sh - archery docker run ubuntu-cpp-odbc + archery docker run ${{ matrix.run-options || '' }} ${{ matrix.image }} + - name: Upload ODBC ${{ matrix.format }} to the job + if: matrix.build-type == 'release' + uses: actions/upload-artifact@v7 + with: + name: flight-sql-odbc-pkg-installer-${{ matrix.format }} + path: build/cpp/ArrowFlightSqlOdbcODBC-*.${{ matrix.format }} + if-no-files-found: error - name: Docker Push if: >- success() && @@ -404,7 +420,7 @@ jobs: ARCHERY_DOCKER_USER: ${{ secrets.DOCKERHUB_USER }} ARCHERY_DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} continue-on-error: true - run: archery docker push ubuntu-cpp-odbc + run: archery docker push ${{ matrix.image }} odbc-macos: needs: check-labels diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0544ff11bf8..dd9b1060795 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -347,6 +347,7 @@ repos: ?^cpp/build-support/update-thrift\.sh$| ?^cpp/examples/minimal_build/run\.sh$| ?^cpp/examples/tutorial_examples/run\.sh$| + ?^cpp/src/arrow/flight/sql/odbc/install/linux/rpm/postinstall$| ?^cpp/src/arrow/flight/sql/odbc/install/unix/install_odbc\.sh$| ?^dev/release/05-binary-upload\.sh$| ?^dev/release/08-binary-verify\.sh$| diff --git a/ci/docker/ubuntu-24.04-cpp.dockerfile b/ci/docker/ubuntu-24.04-cpp.dockerfile index 074301b472d..eca5495b12f 100644 --- a/ci/docker/ubuntu-24.04-cpp.dockerfile +++ b/ci/docker/ubuntu-24.04-cpp.dockerfile @@ -118,6 +118,7 @@ RUN apt-get update -y -q && \ python3-venv \ rados-objclass-dev \ rapidjson-dev \ + rpm \ rsync \ tzdata \ tzdata-legacy \ diff --git a/ci/scripts/cpp_build.sh b/ci/scripts/cpp_build.sh index 1e2f3e8f8f1..5023c30a5df 100755 --- a/ci/scripts/cpp_build.sh +++ b/ci/scripts/cpp_build.sh @@ -274,6 +274,7 @@ else -Dlz4_SOURCE=${lz4_SOURCE:-} \ -Dnlohmann_json_SOURCE=${nlohmann_json_SOURCE:-} \ -Dopentelemetry-cpp_SOURCE=${opentelemetry_cpp_SOURCE:-} \ + -DODBC_PACKAGE_FORMAT=${ODBC_PACKAGE_FORMAT:-} \ -DORC_SOURCE=${ORC_SOURCE:-} \ -DPARQUET_BUILD_EXAMPLES=${PARQUET_BUILD_EXAMPLES:-OFF} \ -DPARQUET_BUILD_EXECUTABLES=${PARQUET_BUILD_EXECUTABLES:-OFF} \ diff --git a/compose.yaml b/compose.yaml index be32a95dd94..e903fed82c7 100644 --- a/compose.yaml +++ b/compose.yaml @@ -516,12 +516,15 @@ services: ARROW_PARQUET: "OFF" ARROW_S3: "OFF" ARROW_SUBSTRAIT: "OFF" + # Use `/arrow/build` so build artifacts are visible on the CI host + # Generate ODBC installer before testing # Register ODBC before running tests command: > /bin/bash -c " - /arrow/ci/scripts/cpp_build.sh /arrow /build && + /arrow/ci/scripts/cpp_build.sh /arrow /arrow/build && + (cd /arrow/build/cpp && cpack) && sudo /arrow/cpp/src/arrow/flight/sql/odbc/install/unix/install_odbc.sh /usr/local/lib/libarrow_flight_sql_odbc.so && - /arrow/ci/scripts/cpp_test.sh /arrow /build" + /arrow/ci/scripts/cpp_test.sh /arrow /arrow/build" ubuntu-cpp-minimal: # Arrow build with minimal components/dependencies diff --git a/cpp/cmake_modules/DefineOptions.cmake b/cpp/cmake_modules/DefineOptions.cmake index 017a5a6efb2..260dc4e9da6 100644 --- a/cpp/cmake_modules/DefineOptions.cmake +++ b/cpp/cmake_modules/DefineOptions.cmake @@ -343,6 +343,12 @@ takes precedence over ccache if a storage backend is configured" ON) ARROW_FLIGHT_SQL ARROW_COMPUTE) + define_option(ARROW_FLIGHT_SQL_ODBC_INSTALLER + "Build the installer Arrow Flight SQL ODBC extension" + OFF + DEPENDS + ARROW_FLIGHT_SQL_ODBC) + define_option(ARROW_GANDIVA "Build the Gandiva libraries" OFF diff --git a/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt index 4227873706f..1ca7c13734d 100644 --- a/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/CMakeLists.txt @@ -159,6 +159,85 @@ if(ARROW_FLIGHT_SQL_ODBC_INSTALLER) set(CPACK_WIX_UI_BANNER "${CMAKE_CURRENT_SOURCE_DIR}/install/windows/arrow-wix-banner.bmp") + else() + set(ODBC_UNIX_FILE_NAME + "ArrowFlightSqlOdbcODBC-${CPACK_PACKAGE_VERSION_MAJOR}.${ODBC_PACKAGE_VERSION_MINOR}.${ODBC_PACKAGE_VERSION_PATCH}" + ) + if(APPLE) + set(CPACK_PACKAGE_FILE_NAME "${ODBC_UNIX_FILE_NAME}") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") + + set(CPACK_SET_DESTDIR ON) + set(CPACK_INSTALL_PREFIX "/Library/ODBC") + # Register ODBC after install + set(CPACK_POSTFLIGHT_ARROW_FLIGHT_SQL_ODBC_SCRIPT + "${CMAKE_CURRENT_SOURCE_DIR}/install/mac/postinstall") + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/install/mac/README.txt") + set(CPACK_RESOURCE_FILE_WELCOME + "${CMAKE_CURRENT_SOURCE_DIR}/install/mac/Welcome.txt") + + set(ODBC_INSTALL_DIR "arrow-odbc/lib") + set(DOC_INSTALL_DIR "arrow-odbc/doc") + else() + # Linux + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}") + if(${ODBC_PACKAGE_FORMAT} STREQUAL "DEB") + # GH-49595 TODO: implement DEB installer + message(STATUS "ODBC_PACKAGE_FORMAT DEB not implemented, see GH-49595") + elseif(${ODBC_PACKAGE_FORMAT} STREQUAL "RPM") + set(CPACK_RPM_PACKAGE_ARCHITECTURE x86_64) + set(CPACK_GENERATOR RPM) + set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/install/linux/rpm/postinstall") + set(CPACK_RPM_FILE_NAME "${ODBC_UNIX_FILE_NAME}.rpm") + # Disable dependency check as ODBC embeds all third party dependencies + set(CPACK_RPM_PACKAGE_AUTOREQPROV "no") + set(CPACK_COMPONENTS_ALL_IN_ONE_PACKAGE 1) + set(CPACK_RPM_COMPONENT_INSTALL ON) + else() + message(FATAL_ERROR "ODBC_PACKAGE_FORMAT '${ODBC_PACKAGE_FORMAT}' must be DEB or RPM for Linux installer." + ) + endif() + + # By default, Linux installs under /usr + set(ODBC_INSTALL_DIR "lib64/arrow-odbc/lib") + set(DOC_INSTALL_DIR "lib64/arrow-odbc/doc") + endif() + + # Install ODBC + install(TARGETS arrow_flight_sql_odbc_shared + DESTINATION "${ODBC_INSTALL_DIR}" + COMPONENT arrow_flight_sql_odbc) + + # Install temporary driver registration scripts, they will be removed after driver registration is complete + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/install/unix/install_odbc.sh" + DESTINATION "${ODBC_INSTALL_DIR}" + COMPONENT arrow_flight_sql_odbc + PERMISSIONS OWNER_EXECUTE + OWNER_WRITE + OWNER_READ + GROUP_EXECUTE + GROUP_READ + WORLD_EXECUTE + WORLD_READ) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/install/unix/install_odbc_ini.sh" + DESTINATION "${ODBC_INSTALL_DIR}" + COMPONENT arrow_flight_sql_odbc + PERMISSIONS OWNER_EXECUTE + OWNER_WRITE + OWNER_READ + GROUP_EXECUTE + GROUP_READ + WORLD_EXECUTE + WORLD_READ) + + # Install documentation files + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../LICENSE.txt" + DESTINATION "${DOC_INSTALL_DIR}" + COMPONENT Docs) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/Connection-Options.md" + DESTINATION "${DOC_INSTALL_DIR}" + COMPONENT Docs) endif() get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) diff --git a/cpp/src/arrow/flight/sql/odbc/README.md b/cpp/src/arrow/flight/sql/odbc/README.md index a2a3fd79041..9261fbd3bb1 100644 --- a/cpp/src/arrow/flight/sql/odbc/README.md +++ b/cpp/src/arrow/flight/sql/odbc/README.md @@ -125,6 +125,22 @@ After ODBC has been registered, you can run the ODBC tests. It is recommended to .\cpp\build\< release | debug >\< Release | Debug>\arrow-flight-sql-odbc-test.exe ``` +## Installers + +ODBC installers are uploaded to the CI artifacts. + +| Operating System | Package Format | +|------------------|----------------| +| Windows | MSI | +| macOS | PKG | +| Linux | DEB / RPM | + +### Install `.RPM` on Ubuntu +While installing via `.DEB` installer on Ubuntu is the recommended approach, users may install `.RPM` on Ubuntu using below command +``` +alien -i --scripts ArrowFlightSqlOdbcODBC-.rpm +``` + ## Known Limitations - Conversion from timestamp data type with specified time zone value to strings is not supported at the moment. This doesn't impact driver's usage of retrieving timestamp data from Power BI on Windows, and Excel on macOS and Windows. See GH-47504 for more context. diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/rpm/postinstall b/cpp/src/arrow/flight/sql/odbc/install/linux/rpm/postinstall new file mode 100755 index 00000000000..df20fab84a9 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/rpm/postinstall @@ -0,0 +1,30 @@ +#!/bin/bash +# +# 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. + +# Use temporary driver registration script to register ODBC driver in system DSN +odbc_install_script="/usr/lib64/arrow-odbc/lib/install_odbc.sh" +"$odbc_install_script" /usr/lib64/arrow-odbc/lib/libarrow_flight_sql_odbc.so + +# Use temporary DSN registration script to register sample system DSN +dsn_install_script="/usr/lib64/arrow-odbc/lib/install_odbc_ini.sh" +"$dsn_install_script" /etc/odbc.ini + +# clean temporary script +rm -f "$odbc_install_script" +rm -f "$dsn_install_script" diff --git a/cpp/src/arrow/flight/sql/odbc/install/unix/install_odbc.sh b/cpp/src/arrow/flight/sql/odbc/install/unix/install_odbc.sh index 5ddcc8a4cbd..de573b7077d 100755 --- a/cpp/src/arrow/flight/sql/odbc/install/unix/install_odbc.sh +++ b/cpp/src/arrow/flight/sql/odbc/install/unix/install_odbc.sh @@ -42,20 +42,20 @@ fi case "$(uname)" in Linux) - USER_ODBCINST_FILE="/etc/odbcinst.ini" + SYSTEM_ODBCINST_FILE="/etc/odbcinst.ini" ;; *) # macOS - USER_ODBCINST_FILE="$HOME/Library/ODBC/odbcinst.ini" - mkdir -p "$HOME"/Library/ODBC + SYSTEM_ODBCINST_FILE="/Library/ODBC/odbcinst.ini" + mkdir -p /Library/ODBC ;; esac DRIVER_NAME="Apache Arrow Flight SQL ODBC Driver" -touch "$USER_ODBCINST_FILE" +touch "$SYSTEM_ODBCINST_FILE" -if grep -q "^\[$DRIVER_NAME\]" "$USER_ODBCINST_FILE"; then +if grep -q "^\[$DRIVER_NAME\]" "$SYSTEM_ODBCINST_FILE"; then echo "Driver [$DRIVER_NAME] already exists in odbcinst.ini" else echo "Adding [$DRIVER_NAME] to odbcinst.ini..." @@ -63,17 +63,24 @@ else [$DRIVER_NAME] Description=An ODBC Driver for Apache Arrow Flight SQL Driver=$ODBC_64BIT -" >>"$USER_ODBCINST_FILE" +" >>"$SYSTEM_ODBCINST_FILE" fi # Check if [ODBC Drivers] section exists -if grep -q '^\[ODBC Drivers\]' "$USER_ODBCINST_FILE"; then +if grep -q '^\[ODBC Drivers\]' "$SYSTEM_ODBCINST_FILE"; then # Section exists: check if driver entry exists - if ! grep -q "^${DRIVER_NAME}=" "$USER_ODBCINST_FILE"; then + if ! grep -q "^${DRIVER_NAME}=" "$SYSTEM_ODBCINST_FILE"; then # Driver entry does not exist, add under [ODBC Drivers] - sed -i '' "/^\[ODBC Drivers\]/a\\ -${DRIVER_NAME}=Installed -" "$USER_ODBCINST_FILE" + + awk -v driver="$DRIVER_NAME" ' + $0 ~ /^\[ODBC Drivers\]/ && !inserted { + print + print driver "=Installed" + inserted=1 + next + } + { print } + ' "$SYSTEM_ODBCINST_FILE" > "${SYSTEM_ODBCINST_FILE}.tmp" && mv "${SYSTEM_ODBCINST_FILE}.tmp" "$SYSTEM_ODBCINST_FILE" fi else # Section doesn't exist, append both section and driver entry at end @@ -81,5 +88,5 @@ else echo "" echo "[ODBC Drivers]" echo "${DRIVER_NAME}=Installed" - } >>"$USER_ODBCINST_FILE" + } >>"$SYSTEM_ODBCINST_FILE" fi