From c78431b422c18515fd95be226827df8c06a25be7 Mon Sep 17 00:00:00 2001 From: singhpratech <42719720+singhpratech@users.noreply.github.com> Date: Sun, 4 Oct 2026 12:51:59 -0400 Subject: [PATCH 1/2] install.sh --drivers: bootstrap the open-licence ODBC drivers sqliteodbc, psqlodbc and MariaDB Connector/ODBC through apt, dnf or Homebrew (their runtime libraries have to be on the loader path, which only the package manager arranges, and the package registers the driver name); clickhouse-odbc from the project's pinned release tarball with its SHA-256 checked, unpacked under $PREFIX/odbc-drivers. --drivers= narrows it, --drivers-only skips the build. The run ends with a table of path, registered name and a load check per driver, plus a connection string each. Docs: install guides, README, roadmap row done (0.1.4; Windows counterpart next), FAQ. Verified in fresh Ubuntu 24.04 and Fedora 42 containers as a non-root user with sudo: all four install; SQLite, PostgreSQL 16 (psqlodbc) and ClickHouse 26.7 queries run through the bridge with the drivers it installed. --- README.md | 12 +- docs/ROADMAP.md | 3 +- docs/community/faq.md | 5 +- docs/getting-started/install-linux.md | 32 ++- docs/getting-started/install-macos.md | 11 +- install.sh | 334 +++++++++++++++++++++++--- 6 files changed, 354 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index e386ed25..2ea65334 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,8 @@ you get native speed from the same install. ## Quick start ```sh -./install.sh # build + install into ~/.local, no root +./install.sh --drivers # build + install into ~/.local; --drivers adds the + # SQLite, PostgreSQL, MariaDB and ClickHouse ODBC drivers pip install adbc-driver-manager pyarrow ``` @@ -295,12 +296,11 @@ place they looked. ## Status and roadmap Early (0.1.3). Working: everything under *What it does*, on Linux, macOS (arm64) and -Windows (x64 and Win32 built and tested in CI on every push; the Windows build lacks -prefetch and parallel ingest); 0.1.3 on PyPI, crates.io, nuget.org and Maven Central. The ADBC Driver +Windows (x64 and Win32 built and tested in CI on every push); 0.1.3 on PyPI, crates.io, +nuget.org and Maven Central. The ADBC Driver Foundry validation suite passes on PostgreSQL apart from declared server limits -([`tests/validation/RESULTS.md`](tests/validation/RESULTS.md)). Next: a driver bootstrap for -the open-licence ODBC drivers, the Win32 thread shim; then a JDBC bridge on the same model — -[`docs/ROADMAP.md`](docs/ROADMAP.md). +([`tests/validation/RESULTS.md`](tests/validation/RESULTS.md)). Next: a JDBC bridge on the +same model — [`docs/ROADMAP.md`](docs/ROADMAP.md). ## Upstream: giving back diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 0769cd79..686d3528 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -21,7 +21,8 @@ ODBC is the first bridge; the name leaves room for the others. | Windows, to verify on the next Windows session (reported by the second Windows campaign, not reproduced on a machine still available): (1) whether `cmake --install` under MSVC can write the manifest key `windows_amd64_mingw` (CMake's `MINGW` is only set for a GNU toolchain; the campaign's own build log recorded `windows_amd64`); (2) the Go binding's SQLite test leaves the database file open after `Close` so `t.TempDir()` cleanup fails — the drivermgr `Close` calls `AdbcConnectionRelease`/`AdbcDatabaseRelease` synchronously, so the open handle is either a driver-side pool or a missing release in the test's reader path | next | | Connection-level reader options (`adbc.odbc.batch_size`, `sqllen_32bit`) set before AdbcConnectionInit are discarded — `OdbcConnectionInit` copies the database defaults over them; re-apply the recorded pre-options after the copy | next | | Windows: prefetch pipeline and parallel ingest on Win32 threads (SRWLOCK, CONDITION_VARIABLE, `_beginthreadex`), the same options and behaviour as Linux and macOS | done (0.1.4) | -| **Driver bootstrap**: `install.sh` / the Windows and macOS installers fetch the open-licence ODBC drivers a first run needs (sqliteodbc, psqlodbc, MariaDB Connector/ODBC, clickhouse-odbc) so SQLite/PostgreSQL/MySQL work with nothing else installed; vendor drivers (Oracle, Db2, SQL Server, Snowflake…) stay the user's download — their licences do not allow redistribution, and Windows already ships the SQL Server driver | next | +| **Driver bootstrap**: `install.sh --drivers` installs the open-licence ODBC drivers a first run needs (sqliteodbc, psqlodbc, MariaDB Connector/ODBC through apt, dnf or Homebrew; clickhouse-odbc from its pinned, checksummed release tarball) and prints what landed where with a connection string each; vendor drivers (Oracle, Db2, SQL Server, Snowflake…) stay the user's download — their licences do not allow redistribution, and Windows already ships the SQL Server driver | done (0.1.4), Linux and macOS | +| Driver bootstrap on Windows: a PowerShell counterpart that runs the vendors' MSI installers (psqlodbc, sqliteodbc, MariaDB Connector/ODBC, clickhouse-odbc) | next | | ADBC Driver Foundry validation suite ([`tests/validation/RESULTS.md`](../tests/validation/RESULTS.md)): PostgreSQL 293/327 pass with the other 34 declared backend limitations (2026-09-15; was 265 on 2026-09-06), SQLite 212/327 with the remainder being precision SQLiteODBC does not report; baseline findings D1–D9, D11, D14 and the PostgreSQL findings P1–P4 (TIME/TIMESTAMP precision and zone fidelity), P7 (`db_schema` option) and P8 (schema catalog fill) fixed; `tests/test_postgres.py` pins them in CI | done — released in 0.1.2 (2026-09-15) | | Trust artefacts: security policy with private vulnerability reporting and a stated support window (`SECURITY.md`); Dependabot on every manifest; `SHA256SUMS` and its signature (`SHA256SUMS.asc`) on v0.1.0; every later release signed, attested (GitHub build provenance, `gh attestation verify`) and shipped with an SPDX SBOM by the release workflow | done | | Foundry listing (adbc-drivers.org) | next | diff --git a/docs/community/faq.md b/docs/community/faq.md index 2b93565e..6d94a558 100644 --- a/docs/community/faq.md +++ b/docs/community/faq.md @@ -386,9 +386,8 @@ the [roadmap](../ROADMAP.md). ### What is on the roadmap? -For the ODBC bridge: a driver-bootstrap installer that fetches the open-licence -drivers, Maven Central publication, the ADBC Driver Foundry validation -suite. Beyond ODBC: a +For the ODBC bridge: the Windows counterpart of `install.sh --drivers`, and the +items in the roadmap's "now" table. Beyond ODBC: a **JDBC bridge** (load a JVM in-process and drive any JDBC driver) and, later, an **OLE DB bridge** for Windows. Full detail and status in [`docs/ROADMAP.md`](../ROADMAP.md). diff --git a/docs/getting-started/install-linux.md b/docs/getting-started/install-linux.md index 45c5578b..3158c2e7 100644 --- a/docs/getting-started/install-linux.md +++ b/docs/getting-started/install-linux.md @@ -147,14 +147,39 @@ installs into your home directory, and writes the manifest into the ADBC user config directory so discovery just works. ```sh -./install.sh +./install.sh # the bridge only +./install.sh --drivers # the bridge plus the four open-licence ODBC drivers below ``` It puts the library in `~/.local/lib/libadbc_driver_odbc.so` (`lib64` on Fedora/RHEL-style 64-bit systems) and the manifest in `~/.config/adbc/drivers/odbc.toml`, then prints both paths. Re-running it is -safe — it reconfigures the same build tree and overwrites the same two files. It -honours these environment overrides: +safe — it reconfigures the same build tree and overwrites the same two files. + +**`--drivers`** also installs the drivers a first run usually needs, so SQLite, +PostgreSQL, MySQL/MariaDB and ClickHouse work with nothing else fetched by hand: + +| Database | Driver | How it is installed | +|---|---|---| +| SQLite | sqliteodbc | `apt install libsqliteodbc` / `dnf install sqliteodbc`, through `sudo` | +| PostgreSQL | psqlodbc | `apt install odbc-postgresql` / `dnf install postgresql-odbc` | +| MySQL / MariaDB | MariaDB Connector/ODBC | `apt install odbc-mariadb` / `dnf install mariadb-connector-odbc` | +| ClickHouse | clickhouse-odbc | the project's release tarball (pinned version, SHA-256 checked), unpacked under `~/.local/odbc-drivers`, no root | + +The first three go through the system package manager on purpose: their +runtime libraries (`libpq`, `libmariadb`) have to be on the loader path, which +only the package manager arranges, and the package registers the driver's name +(`SQLite3`, `PostgreSQL Unicode`, `MariaDB Unicode`) in `odbcinst.ini` so it +works after `Driver=`. clickhouse-odbc has no distribution package and links its +C++ runtime in, so it is unpacked for the current user and used by path. The +script ends with a table of what landed where, whether each library loads, and +a connection string for each. `--drivers=sqlite,postgres` limits it to some of +the four; `--drivers-only` skips the build when the bridge is already installed. +Vendor drivers whose licences do not allow redistribution (Oracle, Db2, SQL +Server, Snowflake, …) stay a separate download — section 2 above and the +[compatibility matrix](../COMPATIBILITY.md) name each one. + +`install.sh` honours these environment overrides: | Variable | Meaning | Default | |---|---|---| @@ -163,6 +188,7 @@ honours these environment overrides: | `BUILD_DIR` | CMake build tree | `/build` | | `BUILD_TYPE` | CMake build type | `Release` | | `JOBS` | parallel build jobs | `nproc` | +| `SUDO` | command that runs the package manager as root for `--drivers` | `sudo` (empty when already root) | > **Troubleshooting:** If `install.sh` stops with `cmake not found`, install the > build prerequisites first — on Debian/Ubuntu `sudo apt install cmake diff --git a/docs/getting-started/install-macos.md b/docs/getting-started/install-macos.md index bc2d9aa9..0f89e92e 100644 --- a/docs/getting-started/install-macos.md +++ b/docs/getting-started/install-macos.md @@ -156,7 +156,8 @@ as shown in [the iODBC section](#when-you-need-a-bridge-built-against-iodbc-inst ### Via install.sh ```sh -./install.sh +./install.sh # the bridge only +./install.sh --drivers # the bridge plus sqliteodbc, psqlodbc, MariaDB Connector/ODBC and clickhouse-odbc ``` On macOS `install.sh` writes the library under `~/.local/lib` and the manifest @@ -165,6 +166,14 @@ into the ADBC user config directory, which on macOS is `MANIFEST_DIR`, `BUILD_DIR`, `BUILD_TYPE` and `JOBS` overrides from the [Linux page](install-linux.md#via-installsh) apply unchanged. +`--drivers` installs the three unixODBC drivers through Homebrew (`sqliteodbc`, +`psqlodbc`, `mariadb-connector-odbc`, no root) and unpacks clickhouse-odbc's +macOS release tarball (pinned version, SHA-256 checked) under +`~/.local/odbc-drivers`. Homebrew formulae do not register driver names in +`odbcinst.ini`, so the script prints each library's path, which is what goes +after `Driver=`. The iODBC-only drivers in the table above are not part of this; +they need the second bridge build described there. + ## The ADBC driver manifest The manifest works exactly as on [Linux](install-linux.md#the-adbc-driver-manifest), diff --git a/install.sh b/install.sh index a3c7fe47..e1df40ff 100755 --- a/install.sh +++ b/install.sh @@ -27,6 +27,14 @@ # Re-running is safe: it reconfigures the same build tree and overwrites the # same two files. # +# Usage: +# ./install.sh build and install the bridge +# ./install.sh --drivers ... and the open-licence ODBC drivers a first +# run needs: SQLite, PostgreSQL, MySQL/MariaDB +# and ClickHouse (see "Driver bootstrap" below) +# ./install.sh --drivers=sqlite,postgres only those two +# ./install.sh --drivers-only just the drivers, the bridge is already built +# # Environment overrides: # PREFIX install prefix for the library (default $HOME/.local) # MANIFEST_DIR directory to write odbc.toml into (default the ADBC user @@ -35,6 +43,27 @@ # BUILD_DIR CMake build tree (default /build) # BUILD_TYPE CMake build type (default Release) # JOBS parallel build jobs (default nproc) +# SUDO command that runs a package manager as root (default sudo; +# set it empty when already root) +# +# Driver bootstrap (--drivers). The bridge needs an ODBC driver per database, +# and the first ones most people want are open-licence and packaged everywhere: +# +# SQLite sqliteodbc Debian libsqliteodbc, Fedora sqliteodbc, +# Homebrew sqliteodbc +# PostgreSQL psqlodbc odbc-postgresql / postgresql-odbc / psqlodbc +# MySQL/MariaDB MariaDB Connector/ODBC odbc-mariadb / mariadb-connector-odbc / +# mariadb-connector-odbc +# ClickHouse clickhouse-odbc the project's own release tarball, pinned +# below and checked against its SHA-256, +# unpacked under $PREFIX/odbc-drivers +# +# The first three come from the system package manager (apt or dnf, through +# sudo, or Homebrew with no root) because their runtime libraries -- libpq, +# libmariadb -- have to be on the loader path, which only the package manager +# can arrange; the package also registers the driver name in odbcinst.ini. +# Vendor drivers whose licences forbid redistribution (Oracle, Db2, SQL Server, +# Snowflake, ...) stay the user's download; the install guides list them. set -euo pipefail @@ -43,6 +72,31 @@ here="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" PREFIX="${PREFIX:-$HOME/.local}" BUILD_DIR="${BUILD_DIR:-$here/build}" BUILD_TYPE="${BUILD_TYPE:-Release}" +SUDO="${SUDO-sudo}" +if [ "$(id -u)" = 0 ]; then SUDO=""; fi + +# clickhouse-odbc has no distribution package; this is the release the bridge +# was verified against, with the checksums the project publishes next to the +# tarballs (clickhouse-odbc--.tar.xz.sha256 inside each release zip). +CH_ODBC_TAG="v1.5.5.20260810" +CH_ODBC_VER="1.5.5" +CH_ODBC_SHA256_LINUX="647d9eb31fb44b311f2b2241196ca0920167124627a745a90a0fac55e2cd226a" +CH_ODBC_SHA256_DARWIN="a60f83dc02600df2b8e1a4076ecfdccd7221d4031cea1c9e89c3f3a0118de168" + +want_bridge=1 +want_drivers=0 +drivers="sqlite,postgres,mysql,clickhouse" +for arg in "$@"; do + case "$arg" in + --drivers) want_drivers=1 ;; + --drivers=*) want_drivers=1; drivers="${arg#--drivers=}" ;; + --drivers-only) want_drivers=1; want_bridge=0 ;; + -h|--help) + sed -n '/^# Usage:/,/^# Driver bootstrap/p' "$0" | sed '$d' | sed 's/^# \{0,1\}//' + exit 0 ;; + *) echo "install.sh: unknown argument $arg (try --help)" >&2; exit 2 ;; + esac +done if [ -z "${MANIFEST_DIR:-}" ]; then # Mirror InternalAdbcUserConfigDir() in the driver manager: macOS uses @@ -64,40 +118,235 @@ if [ -z "${JOBS:-}" ]; then fi fi -command -v cmake >/dev/null 2>&1 || { - echo "install.sh: cmake not found. Install it first:" >&2 - echo " Debian/Ubuntu: sudo apt install cmake unixodbc-dev" >&2 - echo " macOS: brew install cmake unixodbc" >&2 - exit 1 -} +# --------------------------------------------------------------------------- +# The bridge -echo "==> Configuring (prefix $PREFIX)" -# ADBCBRIDGE_MANIFEST_DIR is absolute here, so the manifest lands in the user -# config dir while the library still goes under $PREFIX. -cmake -S "$here" -B "$BUILD_DIR" \ - -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ - -DCMAKE_INSTALL_PREFIX="$PREFIX" \ - -DADBCBRIDGE_MANIFEST_DIR="$MANIFEST_DIR" +install_bridge() { + command -v cmake >/dev/null 2>&1 || { + echo "install.sh: cmake not found. Install it first:" >&2 + echo " Debian/Ubuntu: sudo apt install cmake unixodbc-dev" >&2 + echo " Fedora/RHEL: sudo dnf install cmake unixODBC-devel" >&2 + echo " macOS: brew install cmake unixodbc" >&2 + exit 1 + } -echo "==> Building" -cmake --build "$BUILD_DIR" -j "$JOBS" + echo "==> Configuring (prefix $PREFIX)" + # ADBCBRIDGE_MANIFEST_DIR is absolute here, so the manifest lands in the user + # config dir while the library still goes under $PREFIX. + cmake -S "$here" -B "$BUILD_DIR" \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DADBCBRIDGE_MANIFEST_DIR="$MANIFEST_DIR" -echo "==> Installing" -cmake --install "$BUILD_DIR" + echo "==> Building" + cmake --build "$BUILD_DIR" -j "$JOBS" -manifest="$MANIFEST_DIR/odbc.toml" -# Read the library path back out of the manifest we just wrote, so what we -# print is what the driver manager will actually load. Match on the library -# basename rather than on "first quoted value": the metadata keys above -# [Driver.shared] (name, version, url, ...) are quoted the same way. -lib="" -if [ -f "$manifest" ]; then - lib="$(grep -o "'[^']*libadbc_driver_odbc[^']*'" "$manifest" | tr -d "'" | head -n1)" + echo "==> Installing" + cmake --install "$BUILD_DIR" +} + +# The library path, read back out of the manifest so that what we print is what +# the driver manager will actually load. Match on the library basename rather +# than on "first quoted value": the metadata keys above [Driver.shared] (name, +# version, url, ...) are quoted the same way. +bridge_lib() { + local manifest="$MANIFEST_DIR/odbc.toml" + [ -f "$manifest" ] || return 1 + grep -o "'[^']*libadbc_driver_odbc[^']*'" "$manifest" | tr -d "'" | head -n1 +} + +# --------------------------------------------------------------------------- +# Driver bootstrap + +os="$(uname -s)" +pkg_mgr="" +if [ "$os" = "Darwin" ]; then + command -v brew >/dev/null 2>&1 && pkg_mgr=brew +elif command -v apt-get >/dev/null 2>&1 && command -v dpkg >/dev/null 2>&1; then + pkg_mgr=apt +elif command -v dnf >/dev/null 2>&1; then + pkg_mgr=dnf fi +# Package name, per manager, for each driver we know how to fetch. +pkg_for() { # pkg_for -> package name, or "" when this manager has none + case "$pkg_mgr:$1" in + apt:sqlite) echo libsqliteodbc ;; apt:postgres) echo odbc-postgresql ;; apt:mysql) echo odbc-mariadb ;; + dnf:sqlite) echo sqliteodbc ;; dnf:postgres) echo postgresql-odbc ;; dnf:mysql) echo mariadb-connector-odbc ;; + brew:sqlite) echo sqliteodbc ;; brew:postgres) echo psqlodbc ;; brew:mysql) echo mariadb-connector-odbc ;; + *) echo "" ;; + esac +} + +# The driver library's basename, which is the same on every platform. +lib_for() { + case "$1" in + sqlite) echo libsqlite3odbc ;; + postgres) echo psqlodbcw ;; + mysql) echo libmaodbc ;; + clickhouse) echo libclickhouseodbcw ;; + esac +} + +pkg_installed() { # pkg_installed + case "$pkg_mgr" in + apt) dpkg -s "$1" >/dev/null 2>&1 ;; + dnf) rpm -q "$1" >/dev/null 2>&1 ;; + brew) brew list --versions "$1" >/dev/null 2>&1 ;; + *) return 1 ;; + esac +} + +# Where the package put the driver library. +pkg_lib_path() { # pkg_lib_path + local p + case "$pkg_mgr" in + apt) p="$(dpkg -L "$1" 2>/dev/null | grep -E "/$2\.so$" | head -n1)" ;; + dnf) p="$(rpm -ql "$1" 2>/dev/null | grep -E "/$2\.so$" | head -n1)" ;; + brew) p="$(find "$(brew --prefix "$1" 2>/dev/null)" -name "$2.dylib" -o -name "$2.so" 2>/dev/null | head -n1)" ;; + esac + [ -n "${p:-}" ] && [ -e "$p" ] && echo "$p" +} + +# Any shared library the driver needs that the loader cannot find. +missing_deps() { # missing_deps + if [ "$os" = "Darwin" ]; then + return 0 # otool cannot tell "missing" from "found" without loading; skip + elif command -v ldd >/dev/null 2>&1; then + ldd "$1" 2>/dev/null | awk '/not found/ {print $1}' | tr '\n' ' ' + fi +} + +run_pkg_mgr() { # run_pkg_mgr ... + case "$pkg_mgr" in + apt) $SUDO apt-get install -y "$@" ;; + dnf) $SUDO dnf install -y "$@" ;; + brew) brew install "$@" ;; + esac +} + +sha256_of() { + if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' + else shasum -a 256 "$1" | awk '{print $1}'; fi +} + +# clickhouse-odbc: the project publishes one zip per OS holding a tar.xz and its +# .sha256. Unpack under $PREFIX/odbc-drivers; the library is self-contained +# (its C++ runtime is linked in), so no package manager is involved. +install_clickhouse() { + local osname sha url zip dir tarball + case "$os" in + Linux) osname=Linux; sha="$CH_ODBC_SHA256_LINUX"; zip="clickhouse-odbc-linux-Clang-UnixODBC-Release.zip" ;; + Darwin) osname=Darwin; sha="$CH_ODBC_SHA256_DARWIN"; zip="clickhouse-odbc-macos-AppleClang-UnixODBC-Release.zip" ;; + *) echo " no clickhouse-odbc build for $os"; return 1 ;; + esac + if [ "$(uname -m)" != "x86_64" ] && [ "$os" = "Linux" ]; then + echo " clickhouse-odbc publishes Linux binaries for x86_64 only (this is $(uname -m)); build it from source instead" + return 1 + fi + dir="$PREFIX/odbc-drivers" + tarball="clickhouse-odbc-$CH_ODBC_VER-$osname.tar.xz" + local lib="$dir/clickhouse-odbc-$CH_ODBC_VER-$osname/lib/$(lib_for clickhouse)" + if [ "$os" = "Darwin" ]; then lib="$lib.dylib"; else lib="$lib.so"; fi + if [ -e "$lib" ]; then echo " already unpacked: $lib"; echo "$lib" > "$dir/.clickhouse"; return 0; fi + for tool in curl unzip tar; do + command -v $tool >/dev/null 2>&1 || { echo " $tool is needed to fetch clickhouse-odbc"; return 1; } + done + url="https://github.com/ClickHouse/clickhouse-odbc/releases/download/$CH_ODBC_TAG/$zip" + mkdir -p "$dir" + local tmp; tmp="$(mktemp -d)" + echo " downloading $url" + curl -fsSL -o "$tmp/$zip" "$url" || { echo " download failed"; rm -rf "$tmp"; return 1; } + (cd "$tmp" && unzip -oq "$zip" "$tarball") || { echo " $tarball not in the zip"; rm -rf "$tmp"; return 1; } + local got; got="$(sha256_of "$tmp/$tarball")" + if [ "$got" != "$sha" ]; then + echo " SHA-256 mismatch for $tarball: expected $sha, got $got -- not installed" >&2 + rm -rf "$tmp"; return 1 + fi + tar -xJf "$tmp/$tarball" -C "$dir" + rm -rf "$tmp" + [ -e "$lib" ] || { echo " unpacked, but $lib is missing"; return 1; } + echo "$lib" > "$dir/.clickhouse" + echo " unpacked: $lib (SHA-256 verified)" +} + +# Results, one line per driver: "||||" +results=() + +install_drivers() { + echo "==> Drivers ($drivers)" + if [ -z "$pkg_mgr" ]; then + case "$os" in + Darwin) echo " Homebrew is not installed, so sqliteodbc, psqlodbc and mariadb-connector-odbc cannot be fetched: https://brew.sh" ;; + *) echo " no apt or dnf here; install libsqliteodbc / odbc-postgresql / odbc-mariadb (or your distribution's names) by hand" ;; + esac + fi + local d pkg lib path name deps + IFS=, read -r -a wanted <<< "$drivers" + for d in "${wanted[@]}"; do + d="$(echo "$d" | tr -d ' ')" + case "$d" in + sqlite|postgres|mysql) + pkg="$(pkg_for "$d")"; lib="$(lib_for "$d")" + if [ -z "$pkg" ]; then results+=("$d|skipped||| no package manager"); continue; fi + echo " - $d ($pkg)" + if pkg_installed "$pkg"; then + echo " already installed" + else + if [ -n "$SUDO" ] && [ "$pkg_mgr" != brew ] && ! command -v "$SUDO" >/dev/null 2>&1; then + echo " needs root and $SUDO is not available; run as root: $pkg_mgr install $pkg" + results+=("$d|skipped||| run: $pkg_mgr install $pkg"); continue + fi + if ! run_pkg_mgr "$pkg"; then results+=("$d|failed||| $pkg_mgr install $pkg failed"); continue; fi + fi + path="$(pkg_lib_path "$pkg" "$lib" || true)" + if [ -z "$path" ]; then results+=("$d|installed||| $pkg installed but $lib not found in its file list"); continue; fi + deps="$(missing_deps "$path")" + name="$(registered_name "$path")" + if [ -n "$deps" ]; then results+=("$d|broken|$path|$name| missing: $deps") + else results+=("$d|ok|$path|$name|"); fi ;; + clickhouse) + echo " - clickhouse (clickhouse-odbc $CH_ODBC_VER)" + if install_clickhouse; then + path="$(cat "$PREFIX/odbc-drivers/.clickhouse")" + deps="$(missing_deps "$path")" + if [ -n "$deps" ]; then results+=("clickhouse|broken|$path|| missing: $deps") + else results+=("clickhouse|ok|$path||"); fi + else + results+=("clickhouse|failed|||") + fi ;; + *) echo " - $d: not one of sqlite, postgres, mysql, clickhouse"; results+=("$d|skipped||| unknown driver") ;; + esac + done +} + +# The name odbcinst.ini registered the library under, if any ("Driver=" then +# works as well as the path). Distribution packages register themselves; Homebrew +# formulae and the ClickHouse tarball do not, and the path is what to use. +registered_name() { # registered_name + command -v odbcinst >/dev/null 2>&1 || return 0 + local n drv base; base="$(basename "$1")" + for n in $(odbcinst -q -d 2>/dev/null | tr -d '[]' | tr ' ' '\001'); do + n="$(echo "$n" | tr '\001' ' ')" + # Debian registers the bare file name (Driver=psqlodbcw.so) and lets the driver + # manager's search path find it; Fedora writes the full path. Accept either. + drv="$(odbcinst -q -d -n "$n" 2>/dev/null | sed -n 's/^Driver\(64\)\{0,1\}=//p' | head -n1)" + if [ -n "$drv" ] && { [ "$drv" = "$1" ] || [ "$(basename "$drv")" = "$base" ]; }; then echo "$n"; return 0; fi + done +} + +# --------------------------------------------------------------------------- + +[ "$want_bridge" = 1 ] && install_bridge +[ "$want_drivers" = 1 ] && install_drivers + +manifest="$MANIFEST_DIR/odbc.toml" +lib="$(bridge_lib || true)" if [ ! -f "$manifest" ] || [ -z "$lib" ] || [ ! -f "$lib" ]; then - echo "install.sh: install did not produce a usable manifest" >&2 - exit 1 + if [ "$want_bridge" = 1 ]; then + echo "install.sh: install did not produce a usable manifest" >&2; exit 1 + fi + echo "install.sh: the bridge is not installed yet (no $manifest); run ./install.sh first" >&2 fi cat <;Server=127.0.0.1;Port=5432;Database=postgres;Uid=postgres;Pwd=...; + MySQL Driver=;Server=127.0.0.1;Port=3306;Database=test;User=root;Password=...; + ClickHouse Driver=;Url=http://127.0.0.1:8123;Database=default;Uid=default;Pwd=; + +A driver marked "broken" is installed but needs a library the loader cannot find; +the note names it. +EOF +fi + +cat < Date: Sun, 4 Oct 2026 12:53:01 -0400 Subject: [PATCH 2/2] ci: run install.sh --drivers on Linux and check each driver loads --- .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3df28a49..7cd62ce6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -191,6 +191,37 @@ jobs: ADBC_ODBC_DRIVER: ${{ github.workspace }}/build/libadbc_driver_odbc.so run: python tests/test_sqlite.py + # install.sh --drivers: the bridge into ~/.local plus the four open-licence + # drivers (apt for three, ClickHouse's checksummed tarball for the fourth). + # Its own build tree, so the CI build above is left alone; the manifest it + # writes to ~/.config/adbc/drivers is what driver="odbc" resolves through. + - name: Driver bootstrap (install.sh --drivers, Linux) + if: runner.os == 'Linux' + env: + BUILD_DIR: ${{ github.workspace }}/build-install + run: | + set -o pipefail + ./install.sh --drivers | tee install.log + grep -E '^ (sqlite|postgres|mysql|clickhouse) +ok ' install.log | wc -l | grep -qx 4 + python - <<'PY' + import os, glob, ctypes + import adbc_driver_manager.dbapi as dbapi + with dbapi.connect(driver="odbc", db_kwargs={"uri": "Driver=SQLite3;Database=:memory:;"}) as conn: + with conn.cursor() as cur: + cur.execute("SELECT 1 AS one") + assert cur.fetch_arrow_table().to_pydict() == {"one": [1]} + for name in ("PostgreSQL Unicode", "MariaDB Unicode"): + # No server in this job: a driver that loads and answers the handshake is enough. + try: + dbapi.connect(driver="odbc", db_kwargs={"uri": f"Driver={name};Server=127.0.0.1;Port=1;Uid=x;Pwd=x;"}) + except Exception as e: + assert "SQLDriverConnect" in str(e), e # reached the driver; refused by the (absent) server + ch = glob.glob(os.path.expanduser("~/.local/odbc-drivers/clickhouse-odbc-*/lib/libclickhouseodbcw.so")) + assert ch, "clickhouse-odbc not unpacked" + ctypes.CDLL(ch[0]) + print("driver bootstrap OK") + PY + # The Python package (python/) is a thin wrapper around the library built # above; ADBC_ODBC_DRIVER points its driver lookup at that build. - name: Test the Python package (Linux)