diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/BUILDING.md b/cpp/src/arrow/flight/sql/odbc/install/linux/BUILDING.md new file mode 100644 index 000000000000..16fa27532a3d --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/BUILDING.md @@ -0,0 +1,227 @@ +# Apache Arrow Flight SQL ODBC 25.0.1: Linux build and install + +This runbook builds the Apache Arrow Flight SQL ODBC driver from Arrow commit +`beccec0d0c451b7aa3e4530416ac431b3c035c69` and produces an x86_64 Linux shared +library plus a relocatable tar archive. It never puts a Dremio credential in a +source file, command-line argument, shell history, log, or artifact. + +## Tested target + +- Target ABI: Linux x86_64 (`ELF 64-bit LSB shared object, x86-64`). +- Distribution: Ubuntu 24.04 LTS. +- Toolchain: GCC/G++ 13.3.0, CMake 3.28.3, Ninja 1.11.1, glibc 2.39, and + unixODBC 2.3.12. +- Build mode: Release, shared Arrow Flight SQL ODBC driver with Arrow, Flight, + Flight SQL, gRPC, and Protobuf linked statically. libcurl, libodbcinst, the C++ + runtime, and the standard system libraries remain dynamic. + +The repository's `.github/workflows/cpp_extra.yml` defines its `odbc-linux` CI +job as `amd64` on Ubuntu 24.04. The corresponding `ubuntu-cpp-odbc` service in +`compose.yaml` uses bundled dependencies and disables shared dependency linkage. +That is the repository-defined CI target used here. + +The recorded validation used an amd64 Ubuntu container executed through Rosetta +inside an aarch64 Colima Linux VM on Apple Silicon. The compiler, linker, +unixODBC driver manager, driver, smoke-test process, and userspace were all +x86_64. This is an x86_64 ABI validation, but it is not a bare-metal x86_64 +performance test. + +## Prerequisites + +On a native Ubuntu 24.04 x86_64 host, install Git, Docker Engine with Compose, +and CA certificates. Allow roughly 12 GiB RAM and 30 GiB free disk space because +the Unix driver build compiles bundled static dependencies. + +On Apple Silicon, a disposable Colima VM can provide amd64 container execution: + +```bash +brew install colima docker docker-compose +colima start arrow-odbc-linux \ + --arch aarch64 --vm-type vz --vz-rosetta \ + --cpu 10 --memory 12 --disk 80 +``` + +No AWS EC2 resource is required by this procedure. + +## Verify the source + +```bash +git switch --detach beccec0d0c451b7aa3e4530416ac431b3c035c69 +test "$(git rev-parse HEAD)" = beccec0d0c451b7aa3e4530416ac431b3c035c69 +git status --short +``` + +Do not continue from a dirty worktree unless the changes are understood and +intended. The commands below write build products only under +`cpp/build/linux-odbc-validation`. + +## Build the x86_64 driver + +Build the repository-provided Ubuntu 24.04 toolchain image: + +```bash +ARCH=amd64 ARCH_SHORT=amd64 UBUNTU=24.04 \ + docker-compose build ubuntu-cpp-odbc +``` + +Build and install into the disposable container. The build directory is mounted +back to the host so the driver remains available after the container exits. + +```bash +mkdir -p cpp/build/linux-odbc-validation +docker run --rm --platform linux/amd64 \ + -v "$PWD:/arrow" \ + -v "$PWD/cpp/build/linux-odbc-validation:/build" \ + -e ARROW_ACERO=OFF \ + -e ARROW_AZURE=OFF \ + -e ARROW_BUILD_PARALLEL=4 \ + -e ARROW_BUILD_SHARED=ON \ + -e ARROW_BUILD_STATIC=ON \ + -e ARROW_BUILD_TESTS=OFF \ + -e ARROW_BUILD_TYPE=release \ + -e ARROW_BUILD_UTILITIES=OFF \ + -e ARROW_CSV=OFF \ + -e ARROW_DATASET=OFF \ + -e ARROW_DEPENDENCY_SOURCE=BUNDLED \ + -e ARROW_DEPENDENCY_USE_SHARED=OFF \ + -e ARROW_FLIGHT=ON \ + -e ARROW_FLIGHT_SQL=ON \ + -e ARROW_FLIGHT_SQL_ODBC=ON \ + -e ARROW_FLIGHT_SQL_ODBC_INSTALLER=OFF \ + -e ARROW_GANDIVA=OFF \ + -e ARROW_GCS=OFF \ + -e ARROW_HDFS=OFF \ + -e ARROW_HOME=/usr/local \ + -e ARROW_JEMALLOC=OFF \ + -e ARROW_MIMALLOC=OFF \ + -e ARROW_ORC=OFF \ + -e ARROW_PARQUET=OFF \ + -e ARROW_S3=OFF \ + -e ARROW_SUBSTRAIT=OFF \ + -e ARROW_USE_CCACHE=OFF \ + -e CMAKE_BUILD_PARALLEL_LEVEL=4 \ + apache/arrow-dev:amd64-ubuntu-24.04-cpp \ + /arrow/ci/scripts/cpp_build.sh /arrow /build +``` + +The unstripped build result is: + +```text +cpp/build/linux-odbc-validation/cpp/release/libarrow_flight_sql_odbc.so +``` + +## Package and checksum + +The pinned source does not implement a Linux DEB or RPM. In +`cpp/src/arrow/flight/sql/odbc/CMakeLists.txt`, the Linux installer branch emits +explicit TODO messages for both formats, leaves the Linux ODBC and documentation +install directories unset, and selects no Linux CPack generator. Therefore the +validated deliverable is a relocatable tar archive, not a DEB or RPM. + +Create the stripped direct library, tar archive, smoke-test binary, and +`SHA256SUMS` in the Linux-only artifact directory: + +```bash +linux_dir=cpp/src/arrow/flight/sql/odbc/install/linux +mkdir -p "${linux_dir}/artifacts" +docker run --rm --platform linux/amd64 \ + -v "$PWD:/arrow" -w /arrow \ + apache/arrow-dev:amd64-ubuntu-24.04-cpp \ + "${linux_dir}/package.sh" \ + /arrow/cpp/build/linux-odbc-validation/cpp/release/libarrow_flight_sql_odbc.so \ + "/arrow/${linux_dir}/artifacts" + +cd "${linux_dir}/artifacts" +sha256sum --check SHA256SUMS +cd - +``` + +## Install and register with unixODBC + +Ubuntu runtime prerequisites are the `unixodbc`, `odbcinst`, `libcurl4t64`, and +`ca-certificates` packages, plus the standard C/C++ runtime and the transitive +libraries reported by `ldd`. Verify the artifact before install: + +```bash +sha256sum --check SHA256SUMS +tar -xzf apache-arrow-flight-sql-odbc-25.0.1-linux-x86_64.tar.gz +cd apache-arrow-flight-sql-odbc-25.0.1-linux-x86_64 +ldd lib/libarrow_flight_sql_odbc.so +sudo ./install.sh +odbcinst -q -d -n 'Apache Arrow Flight SQL ODBC Driver' +``` + +`install.sh` defaults to `/opt/apache-arrow-flight-sql-odbc/25.0.1` and uses +`odbcinst` to register the absolute library path. Pass a different absolute +prefix as its sole argument if required. Re-running it at the same prefix is +idempotent. It refuses to overwrite a same-name registration at another path; +`uninstall.sh` likewise unregisters only the exact path it owns. + +For a non-root, isolated validation, avoid changing `/etc/odbcinst.ini`: + +```bash +package_root="$PWD/apache-arrow-flight-sql-odbc-25.0.1-linux-x86_64" +mkdir -p "$PWD/odbc-config" +sed "s|@DRIVER_PATH@|${package_root}/lib/libarrow_flight_sql_odbc.so|g" \ + "${package_root}/odbcinst.ini.in" >"$PWD/odbc-config/odbcinst.ini" +export ODBCSYSINI="$PWD/odbc-config" +export ODBCINSTINI=odbcinst.ini +odbcinst -q -d -n 'Apache Arrow Flight SQL ODBC Driver' +``` + +## Run the Dremio Cloud smoke test securely + +The smoke test defaults to `data.eu.dremio.cloud:443`, enables TLS, verifies the +server certificate against the Linux system trust store, and executes +`SELECT 1`. For token authentication, it prefers a short-lived token from the +file named by `DREMIO_ODBC_TOKEN_FILE`. For user/password authentication, it +reads `DREMIO_ODBC_UID` and prefers the secret from +`DREMIO_ODBC_PASSWORD_FILE`. The connection string exists only in process +memory and is never printed. + +Have the secret manager materialize a short-lived secret as a mode-0600 file, +then export only its path. Do not paste the secret into these commands. + +```bash +export DREMIO_ODBC_TOKEN_FILE=/run/secrets/dremio_odbc_token +export DREMIO_ODBC_HOST=data.eu.dremio.cloud +export DREMIO_ODBC_PORT=443 + +./smoke/flight_sql_odbc_smoke_test +``` + +Expected evidence includes `connection: success`, `result: 1`, successful cursor, +statement, connection, and environment cleanup lines, and `smoke test: PASS`. +A successful compile or driver registration alone is not a successful remote +smoke test. + +After the test, revoke the short-lived credential and remove the secret file +through the secret manager. Then clear the process environment: + +```bash +unset DREMIO_ODBC_TOKEN_FILE DREMIO_ODBC_UID DREMIO_ODBC_PASSWORD_FILE \ + DREMIO_ODBC_HOST DREMIO_ODBC_PORT +``` + +## Uninstall and tear down + +From the extracted package directory: + +```bash +sudo ./uninstall.sh +if odbcinst -q -d -n 'Apache Arrow Flight SQL ODBC Driver'; then + echo 'driver registration still exists' >&2 + exit 1 +fi +``` + +Remove only the disposable Colima profile created for this validation: + +```bash +colima stop arrow-odbc-linux +colima delete arrow-odbc-linux +``` + +If a cloud VM is substituted for Colima, record its instance ID, security group, +key, and volume IDs before use, then terminate the instance and delete only those +recorded resources. Confirm that no volume, elastic IP, or security group remains. diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/VALIDATION.md b/cpp/src/arrow/flight/sql/odbc/install/linux/VALIDATION.md new file mode 100644 index 000000000000..6961bf9725f0 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/VALIDATION.md @@ -0,0 +1,145 @@ +# Apache Arrow Flight SQL ODBC 25.0.1 Linux validation + +Validation date: 2026-09-09 + +Source commit: `beccec0d0c451b7aa3e4530416ac431b3c035c69` + +Source tag: `apache-arrow-25.0.1` + +## Outcome + +| Check | Result | +|---|---| +| Exact source and clean starting worktree | PASS | +| Repository-defined Ubuntu 24.04 amd64 build | PASS | +| x86_64 ELF and exported ODBC entry points | PASS | +| Dynamic dependency resolution | PASS | +| TLS CA-chain and hostname verification | PASS | +| Isolated unixODBC registration | PASS | +| Authenticated `SELECT 1` against Dremio Cloud | PASS, 3/3 | +| Cursor, statement, connection, and environment cleanup | PASS, 3/3 | +| Installer idempotence and registration ownership guards | PASS | +| Reproducible archive, checksum, install, load, and uninstall | PASS | +| Credential file removal | PASS | + +## Environment + +- Container image: + `apache/arrow-dev@sha256:a887c3bfb5262539c9046d414a8dfa9390c65358019e760128e8104e86041b36` +- Container architecture: `x86_64` / Debian architecture `amd64`. +- Distribution: Ubuntu 24.04.4 LTS. +- Toolchain: GCC/G++ 13.3.0, CMake 3.28.3, Ninja 1.11.1. +- Runtime: glibc 2.39 and unixODBC 2.3.12. +- Execution host: Apple Silicon, using an aarch64 Colima VM and Rosetta to run + the complete amd64 container userspace. + +The compiler, linker, unixODBC driver manager, driver, and smoke-test process +were x86_64. This validates the Linux x86_64 ABI and end-to-end behavior, but it +is not a native x86_64 performance result. + +## Build evidence + +The build used the same Ubuntu version, architecture, bundled-dependency mode, +and disabled shared dependency linkage as Arrow's `odbc-linux` CI job and +`ubuntu-cpp-odbc` Compose service. CMake reported Arrow 25.0.1, x86_64, and the +Release configuration. The build completed the `install` target successfully. +It began at four-way parallelism and resumed the same Ninja graph at eight-way +parallelism after an intentional clean interrupt; no configuration or source +changed at that boundary. + +The unstripped result was 60.4 MiB and identified as: + +```text +ELF 64-bit LSB shared object, x86-64 +Machine: Advanced Micro Devices X86-64 +Type: DYN (Shared object file) +``` + +The driver exported the expected Unix wide-character entry points, including +`SQLConnectW`, `SQLDriverConnectW`, and `SQLExecDirectW`, plus common entry +points such as `SQLDisconnect`, `SQLFetch`, and `SQLCloseCursor`. + +Direct dynamic dependencies were `libcurl.so.4`, `libodbcinst.so.2`, +`libstdc++.so.6`, `libm.so.6`, `libgcc_s.so.1`, `libc.so.6`, and the x86_64 +loader. `ldd` resolved those and every transitive dependency. No dynamic Arrow, +Flight, Flight SQL, gRPC, or Protobuf library was required. + +## TLS, registration, and query evidence + +An independent OpenSSL preflight against `data.eu.dremio.cloud:443`, with SNI, +`-verify_hostname`, and `-verify_return_error`, reported `Verification: OK` and +`Verify return code: 0 (ok)`. + +The driver was then registered in an isolated mode-0600 `odbcinst.ini`. The +registered `Driver` and `Setup` values both resolved to the just-built shared +library; no host or container system ODBC configuration was modified. + +The compiled smoke client performed three independent cycles. Every cycle +reported: + +```text +connection: success (TLS certificate verification enabled) +query: SELECT 1 +result: 1 +cleanup: close cursor: success +cleanup: free statement: success +cleanup: disconnect: success +cleanup: free connection: success +cleanup: free environment: success +smoke test: PASS +``` + +This directly covers the connect, execute, fetch, cursor close, disconnect, and +handle-destruction path that a compile-only check would miss. + +## Packaging decision + +With `ARROW_FLIGHT_SQL_ODBC_INSTALLER=ON`, the pinned source prints: + +```text +ODBC_PACKAGE_FORMAT DEB not implemented, see GH-49595 +ODBC_PACKAGE_FORMAT RPM not implemented, see GH-47977 +``` + +The Linux branch sets no DEB or RPM generator and contains the additional TODO +to create a Linux installer. Manually forcing CPack's generic TGZ generator did +produce an archive, but inspection showed a generic Arrow library layout and no +unixODBC registration scripts. It is not a complete Linux ODBC installation +artifact. + +The validated deliverable is therefore the versioned x86_64 driver plus a +relocatable tar archive with explicit `install.sh` and `uninstall.sh`, an +`odbcinst.ini` template, the smoke-test source and binary, licenses, this report, +and the build runbook. `SHA256SUMS` authenticates both deliverables. + +The stripped direct driver was 47 MiB and the archive was 16 MiB. Two packaging +runs produced identical SHA-256 values. The exact archive then passed checksum +verification, extraction, installation at the default prefix, repeat +installation with `UsageCount=1`, dynamic loading, documentation installation, +unregistration, and exact-prefix file removal. + +## Installer and credential safety + +The installer tests used an isolated unixODBC configuration and verified: + +- initial registration succeeds; +- reinstall at the same prefix is idempotent and leaves `UsageCount=1`; +- install refuses to replace a same-name driver registered at another path; +- uninstall refuses to remove a same-name driver registered at another path; +- uninstall removes its own registration and exact installed files. + +The Dremio token was never placed in a repository file, ODBC configuration, +command-line argument, or artifact. It was read from a root-owned mode-0600 +container secret file. Diagnostic text was redacted by the smoke client, driver +logging was disabled, and a shell trap deleted the secret file immediately after +the three live attempts. A follow-up existence check passed. Because the token +was originally supplied through an interactive conversation, it should still be +revoked after validation. + +## Limitations + +- Functional and ABI behavior was validated under translated x86_64 container + execution rather than bare-metal x86_64 Linux. +- This is not a performance, load, failover, or broad SQL conformance test. +- Compatibility with distributions older than Ubuntu 24.04 is not established; + the artifact should be treated as an Ubuntu 24.04 / glibc 2.39 build. diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/artifacts/SHA256SUMS b/cpp/src/arrow/flight/sql/odbc/install/linux/artifacts/SHA256SUMS new file mode 100644 index 000000000000..1e93d4002a3f --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/artifacts/SHA256SUMS @@ -0,0 +1,2 @@ +e031eb3d67e10795d83f6a708afb8c5b2a8f07f90f8875c3222fa0de55bebe65 libarrow_flight_sql_odbc-25.0.1-linux-x86_64.so +f60ee12013544e538821783d54a4aaf9578e4c7c86455363ab59ffce3488d8c7 apache-arrow-flight-sql-odbc-25.0.1-linux-x86_64.tar.gz diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/artifacts/apache-arrow-flight-sql-odbc-25.0.1-linux-x86_64.tar.gz b/cpp/src/arrow/flight/sql/odbc/install/linux/artifacts/apache-arrow-flight-sql-odbc-25.0.1-linux-x86_64.tar.gz new file mode 100644 index 000000000000..6c11f0f56d94 Binary files /dev/null and b/cpp/src/arrow/flight/sql/odbc/install/linux/artifacts/apache-arrow-flight-sql-odbc-25.0.1-linux-x86_64.tar.gz differ diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/artifacts/libarrow_flight_sql_odbc-25.0.1-linux-x86_64.so b/cpp/src/arrow/flight/sql/odbc/install/linux/artifacts/libarrow_flight_sql_odbc-25.0.1-linux-x86_64.so new file mode 100755 index 000000000000..4ed940300232 Binary files /dev/null and b/cpp/src/arrow/flight/sql/odbc/install/linux/artifacts/libarrow_flight_sql_odbc-25.0.1-linux-x86_64.so differ diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/build_smoke_test.sh b/cpp/src/arrow/flight/sql/odbc/install/linux/build_smoke_test.sh new file mode 100755 index 000000000000..d6295b24a525 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/build_smoke_test.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env 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. + +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +output="${1:-${script_dir}/flight_sql_odbc_smoke_test}" + +"${CXX:-g++}" -std=c++17 -O2 -Wall -Wextra -Wpedantic -Werror \ + "${script_dir}/smoke_test.cc" -lodbc -o "${output}" diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/install.sh b/cpp/src/arrow/flight/sql/odbc/install/linux/install.sh new file mode 100755 index 000000000000..d39094ae3518 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/install.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env 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. + +set -euo pipefail + +if [[ ${EUID} -ne 0 ]]; then + echo "install.sh must run as root" >&2 + exit 1 +fi +if ! command -v odbcinst >/dev/null 2>&1; then + echo "odbcinst is required (install the odbcinst package)" >&2 + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +prefix="${1:-/opt/apache-arrow-flight-sql-odbc/25.0.1}" +if [[ ${prefix} != /* || ${prefix} == / ]]; then + echo "installation prefix must be an absolute directory other than /" >&2 + exit 1 +fi +library_name="libarrow_flight_sql_odbc.so" +source_library="${script_dir}/lib/${library_name}" +target_library="${prefix}/lib/${library_name}" +driver_name="Apache Arrow Flight SQL ODBC Driver" + +if [[ ! -f ${source_library} ]]; then + echo "driver library is missing from the package" >&2 + exit 1 +fi + +already_registered=false +if existing_registration="$(odbcinst -q -d -n "${driver_name}" 2>/dev/null)"; then + existing_driver="$( + printf '%s\n' "${existing_registration}" | + awk -F= 'tolower($1) == "driver" {sub(/^[^=]*=/, ""); print; exit}' + )" + if [[ ${existing_driver} != "${target_library}" ]]; then + echo "driver name is already registered at a different path: ${existing_driver}" >&2 + exit 1 + fi + already_registered=true +fi + +install -d -m 0755 "${prefix}/lib" "${prefix}/share/doc" +install -m 0755 "${source_library}" "${target_library}" +if [[ -d ${script_dir}/share/doc ]]; then + cp -R "${script_dir}/share/doc/." "${prefix}/share/doc/" +fi + +registration="$(mktemp)" +trap 'rm -f "${registration}"' EXIT +if [[ ${already_registered} == false ]]; then + sed "s|@DRIVER_PATH@|${target_library}|g" \ + "${script_dir}/odbcinst.ini.in" >"${registration}" + odbcinst -i -d -f "${registration}" +fi + +echo "Installed Apache Arrow Flight SQL ODBC Driver at ${target_library}" diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/odbcinst.ini.in b/cpp/src/arrow/flight/sql/odbc/install/linux/odbcinst.ini.in new file mode 100644 index 000000000000..5ddb670276fc --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/odbcinst.ini.in @@ -0,0 +1,6 @@ +[Apache Arrow Flight SQL ODBC Driver] +Description=Apache Arrow Flight SQL ODBC Driver 25.0.1 +Driver=@DRIVER_PATH@ +Setup=@DRIVER_PATH@ +Threading=2 +UsageCount=1 diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/package.sh b/cpp/src/arrow/flight/sql/odbc/install/linux/package.sh new file mode 100755 index 000000000000..60d8755fa82e --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/package.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env 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. + +set -euo pipefail + +if [[ $# -ne 2 ]]; then + echo "Usage: package.sh " >&2 + exit 2 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/../../../../../../../../" && pwd)" +driver="$(realpath "$1")" +output_dir="$(realpath -m "$2")" +version="25.0.1" +package_name="apache-arrow-flight-sql-odbc-${version}-linux-x86_64" +direct_library="libarrow_flight_sql_odbc-${version}-linux-x86_64.so" + +if [[ ! -f ${driver} ]]; then + echo "driver library does not exist: ${driver}" >&2 + exit 1 +fi +if ! file "${driver}" | grep -q 'ELF 64-bit.*x86-64'; then + echo "driver is not a Linux x86_64 ELF shared library" >&2 + exit 1 +fi + +mkdir -p "${output_dir}" +staging="$(mktemp -d)" +trap 'rm -rf "${staging}"' EXIT +package_root="${staging}/${package_name}" +mkdir -p "${package_root}/lib" "${package_root}/share/doc" \ + "${package_root}/smoke" + +install -m 0755 "${driver}" "${output_dir}/${direct_library}" +strip --strip-unneeded "${output_dir}/${direct_library}" +install -m 0755 "${output_dir}/${direct_library}" \ + "${package_root}/lib/libarrow_flight_sql_odbc.so" +install -m 0755 "${script_dir}/install.sh" "${package_root}/install.sh" +install -m 0755 "${script_dir}/uninstall.sh" "${package_root}/uninstall.sh" +install -m 0644 "${script_dir}/odbcinst.ini.in" "${package_root}/odbcinst.ini.in" +install -m 0644 "${script_dir}/BUILDING.md" "${package_root}/share/doc/BUILDING.md" +install -m 0644 "${script_dir}/VALIDATION.md" "${package_root}/share/doc/VALIDATION.md" +install -m 0644 "${script_dir}/../../connection-options.md" \ + "${package_root}/share/doc/connection-options.md" +install -m 0644 "${script_dir}/../../README.md" "${package_root}/share/doc/README.md" +install -m 0644 "${repo_root}/LICENSE.txt" "${package_root}/share/doc/LICENSE.txt" +install -m 0644 "${repo_root}/NOTICE.txt" "${package_root}/share/doc/NOTICE.txt" +install -m 0644 "${script_dir}/smoke_test.cc" "${package_root}/smoke/smoke_test.cc" +install -m 0755 "${script_dir}/build_smoke_test.sh" \ + "${package_root}/smoke/build_smoke_test.sh" +"${script_dir}/build_smoke_test.sh" \ + "${package_root}/smoke/flight_sql_odbc_smoke_test" + +tar --sort=name --mtime='UTC 2026-08-05' --owner=0 --group=0 --numeric-owner \ + -C "${staging}" -czf "${output_dir}/${package_name}.tar.gz" "${package_name}" + +( + cd "${output_dir}" + sha256sum "${direct_library}" "${package_name}.tar.gz" >SHA256SUMS +) diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/smoke_test.cc b/cpp/src/arrow/flight/sql/odbc/install/linux/smoke_test.cc new file mode 100644 index 000000000000..1d0a1ad60a72 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/smoke_test.cc @@ -0,0 +1,236 @@ +// 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 +#include + +#include +#include +#include +#include +#include + +namespace { + +std::string GetRequiredEnv(const char* name) { + const char* value = std::getenv(name); + if (value == nullptr || value[0] == '\0') { + std::cerr << "missing required environment variable: " << name << '\n'; + std::exit(2); + } + return value; +} + +std::string ReadSecret(const char* file_env_name, const char* value_env_name) { + const char* path = std::getenv(file_env_name); + if (path != nullptr && path[0] != '\0') { + std::ifstream input(path); + if (!input) { + std::cerr << "cannot read " << file_env_name << '\n'; + std::exit(2); + } + std::ostringstream contents; + contents << input.rdbuf(); + std::string password = contents.str(); + while (!password.empty() && (password.back() == '\n' || password.back() == '\r')) { + password.pop_back(); + } + if (password.empty()) { + std::cerr << file_env_name << " is empty\n"; + std::exit(2); + } + return password; + } + return GetRequiredEnv(value_env_name); +} + +std::string Redact(std::string message, const std::string& uid, + const std::string& password) { + for (const auto& secret : {uid, password}) { + if (secret.empty()) continue; + for (std::string::size_type position = 0; + (position = message.find(secret, position)) != std::string::npos;) { + message.replace(position, secret.size(), ""); + position += sizeof("") - 1; + } + } + return message; +} + +void PrintDiagnostics(SQLSMALLINT handle_type, SQLHANDLE handle, const std::string& uid, + const std::string& password) { + SQLCHAR state[6] = {}; + SQLCHAR message[2048] = {}; + SQLINTEGER native_error = 0; + SQLSMALLINT message_length = 0; + for (SQLSMALLINT record = 1; + SQL_SUCCEEDED(SQLGetDiagRec(handle_type, handle, record, state, &native_error, + message, sizeof(message), &message_length)); + ++record) { + std::string safe_message(reinterpret_cast(message), message_length); + std::cerr << "ODBC diagnostic: state=" << state << " native=" << native_error + << " message=" << Redact(safe_message, uid, password) << '\n'; + } +} + +bool Check(SQLRETURN result, const char* operation, SQLSMALLINT handle_type, + SQLHANDLE handle, const std::string& uid, const std::string& password) { + if (SQL_SUCCEEDED(result)) { + if (result == SQL_SUCCESS_WITH_INFO) { + PrintDiagnostics(handle_type, handle, uid, password); + } + return true; + } + std::cerr << operation << " failed with SQLRETURN=" << result << '\n'; + if (handle != SQL_NULL_HANDLE) { + PrintDiagnostics(handle_type, handle, uid, password); + } + return false; +} + +bool Cleanup(SQLHENV env, SQLHDBC connection, SQLHSTMT statement, bool connected, + const std::string& uid, const std::string& password) { + bool ok = true; + if (statement != SQL_NULL_HSTMT) { + SQLRETURN result = SQLCloseCursor(statement); + if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO && + result != SQL_NO_DATA) { + ok = Check(result, "SQLCloseCursor", SQL_HANDLE_STMT, statement, uid, password) && + ok; + } else { + std::cout << "cleanup: close cursor: success\n"; + } + result = SQLFreeHandle(SQL_HANDLE_STMT, statement); + if (!SQL_SUCCEEDED(result)) { + std::cerr << "cleanup: free statement: failed with SQLRETURN=" << result << '\n'; + ok = false; + } else { + std::cout << "cleanup: free statement: success\n"; + } + } + if (connection != SQL_NULL_HDBC) { + if (connected) { + SQLRETURN result = SQLDisconnect(connection); + if (!SQL_SUCCEEDED(result)) { + ok = Check(result, "SQLDisconnect", SQL_HANDLE_DBC, connection, uid, password) && + ok; + } else { + std::cout << "cleanup: disconnect: success\n"; + } + } + SQLRETURN result = SQLFreeHandle(SQL_HANDLE_DBC, connection); + if (!SQL_SUCCEEDED(result)) { + std::cerr << "cleanup: free connection: failed with SQLRETURN=" << result << '\n'; + ok = false; + } else { + std::cout << "cleanup: free connection: success\n"; + } + } + if (env != SQL_NULL_HENV) { + SQLRETURN result = SQLFreeHandle(SQL_HANDLE_ENV, env); + if (!SQL_SUCCEEDED(result)) { + std::cerr << "cleanup: free environment: failed with SQLRETURN=" << result << '\n'; + ok = false; + } else { + std::cout << "cleanup: free environment: success\n"; + } + } + return ok; +} + +} // namespace + +int main() { + const char* token_file = std::getenv("DREMIO_ODBC_TOKEN_FILE"); + const char* token_value = std::getenv("DREMIO_ODBC_TOKEN"); + const bool token_auth = (token_file != nullptr && token_file[0] != '\0') || + (token_value != nullptr && token_value[0] != '\0'); + const std::string uid = token_auth ? "" : GetRequiredEnv("DREMIO_ODBC_UID"); + const std::string password = + token_auth ? ReadSecret("DREMIO_ODBC_TOKEN_FILE", "DREMIO_ODBC_TOKEN") + : ReadSecret("DREMIO_ODBC_PASSWORD_FILE", "DREMIO_ODBC_PASSWORD"); + const char* host_value = std::getenv("DREMIO_ODBC_HOST"); + const char* port_value = std::getenv("DREMIO_ODBC_PORT"); + const std::string host = host_value != nullptr ? host_value : "data.eu.dremio.cloud"; + const std::string port = port_value != nullptr ? port_value : "443"; + + const std::string authentication = + token_auth ? ";Token=" + password : ";UID=" + uid + ";PWD=" + password; + const std::string connection_string = + "Driver={Apache Arrow Flight SQL ODBC Driver};Host=" + host + ";Port=" + port + + authentication + + ";useEncryption=true;disableCertificateVerification=false;" + "useSystemTrustStore=true;useWideChar=false;"; + + SQLHENV env = SQL_NULL_HENV; + SQLHDBC connection = SQL_NULL_HDBC; + SQLHSTMT statement = SQL_NULL_HSTMT; + + if (!Check(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env), "SQLAllocHandle(env)", + SQL_HANDLE_ENV, env, uid, password) || + !Check(SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, + reinterpret_cast(SQL_OV_ODBC3), 0), + "SQLSetEnvAttr", SQL_HANDLE_ENV, env, uid, password) || + !Check(SQLAllocHandle(SQL_HANDLE_DBC, env, &connection), + "SQLAllocHandle(connection)", SQL_HANDLE_ENV, env, uid, password)) { + Cleanup(env, connection, statement, false, uid, password); + return 1; + } + + SQLCHAR completed_connection[4096] = {}; + SQLSMALLINT completed_length = 0; + SQLRETURN result = SQLDriverConnect( + connection, nullptr, + reinterpret_cast(const_cast(connection_string.c_str())), SQL_NTS, + completed_connection, sizeof(completed_connection), &completed_length, + SQL_DRIVER_NOPROMPT); + if (!Check(result, "SQLDriverConnect", SQL_HANDLE_DBC, connection, uid, password)) { + Cleanup(env, connection, statement, false, uid, password); + return 1; + } + std::cout << "connection: success (TLS certificate verification enabled)\n"; + + if (!Check(SQLAllocHandle(SQL_HANDLE_STMT, connection, &statement), + "SQLAllocHandle(statement)", SQL_HANDLE_DBC, connection, uid, password) || + !Check(SQLExecDirect(statement, + reinterpret_cast(const_cast("SELECT 1")), + SQL_NTS), + "SQLExecDirect", SQL_HANDLE_STMT, statement, uid, password) || + !Check(SQLFetch(statement), "SQLFetch", SQL_HANDLE_STMT, statement, uid, + password)) { + Cleanup(env, connection, statement, true, uid, password); + return 1; + } + + SQLINTEGER value = 0; + SQLLEN indicator = 0; + if (!Check(SQLGetData(statement, 1, SQL_C_SLONG, &value, sizeof(value), &indicator), + "SQLGetData", SQL_HANDLE_STMT, statement, uid, password)) { + Cleanup(env, connection, statement, true, uid, password); + return 1; + } + std::cout << "query: SELECT 1\nresult: " << value << '\n'; + if (indicator == SQL_NULL_DATA || value != 1) { + std::cerr << "unexpected query result\n"; + Cleanup(env, connection, statement, true, uid, password); + return 1; + } + + const bool cleanup_ok = Cleanup(env, connection, statement, true, uid, password); + std::cout << "smoke test: " << (cleanup_ok ? "PASS" : "FAIL") << '\n'; + return cleanup_ok ? 0 : 1; +} diff --git a/cpp/src/arrow/flight/sql/odbc/install/linux/uninstall.sh b/cpp/src/arrow/flight/sql/odbc/install/linux/uninstall.sh new file mode 100755 index 000000000000..386c3d12c204 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/install/linux/uninstall.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env 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. + +set -euo pipefail + +if [[ ${EUID} -ne 0 ]]; then + echo "uninstall.sh must run as root" >&2 + exit 1 +fi +if ! command -v odbcinst >/dev/null 2>&1; then + echo "odbcinst is required (install the odbcinst package)" >&2 + exit 1 +fi + +prefix="${1:-/opt/apache-arrow-flight-sql-odbc/25.0.1}" +if [[ ${prefix} != /* || ${prefix} == / ]]; then + echo "installation prefix must be an absolute directory other than /" >&2 + exit 1 +fi +driver_name="Apache Arrow Flight SQL ODBC Driver" +target_library="${prefix}/lib/libarrow_flight_sql_odbc.so" + +while registration="$(odbcinst -q -d -n "${driver_name}" 2>/dev/null)"; do + registered_driver="$( + printf '%s\n' "${registration}" | + awk -F= 'tolower($1) == "driver" {sub(/^[^=]*=/, ""); print; exit}' + )" + if [[ ${registered_driver} != "${target_library}" ]]; then + echo "refusing to unregister driver at a different path: ${registered_driver}" >&2 + exit 1 + fi + odbcinst -u -d -n "${driver_name}" +done + +rm -f "${prefix}/lib/libarrow_flight_sql_odbc.so" +rmdir "${prefix}/lib" 2>/dev/null || true +rm -f "${prefix}/share/doc/BUILDING.md" \ + "${prefix}/share/doc/VALIDATION.md" \ + "${prefix}/share/doc/connection-options.md" \ + "${prefix}/share/doc/README.md" \ + "${prefix}/share/doc/LICENSE.txt" \ + "${prefix}/share/doc/NOTICE.txt" +rmdir "${prefix}/share/doc" 2>/dev/null || true +rmdir "${prefix}/share" 2>/dev/null || true +rmdir "${prefix}" 2>/dev/null || true + +echo "Uninstalled Apache Arrow Flight SQL ODBC Driver from ${prefix}"