目 录CONTENT

文章目录

【DuckDB】一条 SQL 同时连接 Oracle 和 GaussDB

DarkAthena
2026-05-23 / 0 评论 / 0 点赞 / 3 阅读 / 0 字

【DuckDB】一条 SQL 同时连接 Oracle 和 GaussDB

前言

在数据迁移、数据比对、异构数据库运维等场景中,经常需要同时查询多个不同类型的数据库。DuckDB 作为一款嵌入式分析型数据库,支持通过扩展连接多种外部数据源。本文将演示如何在一条 SQL 中同时连接 OracleGaussDB,并实现跨库联合查询。

同时,由于连接 Oracle 数据库需要通过 ODBC 驱动,而生产环境通常不允许随意安装系统级 ODBC 包,本文提供了一套完全自包含的部署方案——所有依赖(DuckDB CLI、扩展、Oracle Instant Client、unixODBC 运行库)全部打包在项目目录内,无需修改系统任何配置。

环境说明

已验证环境

环境一: Ubuntu 22.04 x86_64

项目
操作系统Ubuntu 22.04 x86_64
DuckDB 版本v1.5.3
Oracle 数据库Oracle 19c Enterprise Edition
GaussDB 版本506.0.0.SPC0500
Oracle 连接system/oraclepassword@192.168.163.227:1527/pdb1
GaussDB 连接admin/gaussdbpassword@192.168.163.227:15432/postgres

环境二: Kylin V10 x86_64

项目
操作系统Kylin Linux Advanced Server V10 (Lance) x86_64
内核版本4.19.90-52.22.v2207.ky10.x86_64
DuckDB 版本v1.5.3
Oracle 数据库Oracle 19c Enterprise Edition
GaussDB 版本506.0.0.SPC0500
Oracle 连接system/oraclepassword@192.168.163.227:1527/pdb1
GaussDB 连接admin/gaussdbpassword@192.168.163.227:15432/postgres

兼容性声明

本方案设计时考虑了以下兼容性:

  • 操作系统: Debian/Ubuntu 系列 + Fedora/RHEL 系列(含 Kylin、openEuler、Anolis 等国产系统)
  • CPU 架构: x86_64 (amd64) + ARM64 (aarch64)
  • 前提条件: 目标机器需要有网络访问权限(下载依赖),root 权限(安装系统包)

核心原理

DuckDB 连接两种数据库的方式不同:

数据库连接方式DuckDB 扩展依赖
OracleODBCodbc_scannerOracle Instant Client + unixODBC
GaussDBPostgreSQL 协议postgres_scanner无额外依赖

关键技术点:

  1. Oracle 通过 ODBC 连接:使用 DuckDB 的 odbc_connect() + odbc_query() 函数
  2. GaussDB 通过 PostgreSQL 协议连接:使用 DuckDB 的 ATTACH ... AS gaussdb (TYPE postgres) 语法
  3. GaussDB 需要 text 协议:执行 SET pg_use_text_protocol=true; 避免二进制协议兼容问题
  4. 自包含部署:通过环境变量将所有路径指向项目目录

项目结构

duckdb-oracle-gaussdb/
├── install.sh                              # 在线安装脚本(需要外网)
├── install_offline.sh                      # 离线安装脚本(使用 offline_packages/)
├── prepare.sh                              # 离线准备脚本 - Linux/Mac bash 版
├── prepare.ps1                             # 离线准备脚本 - Windows PowerShell 版
├── run.sh                                  # 运行跨库查询(在线/离线通用)
├── env.sh                                  # 环境变量(install.sh 生成,勿手动编辑)
├── offline_packages/                        # 离线依赖包(prepare.sh 生成)
│   ├── VERSION                             # 版本信息
│   └── amd64/                              # 或 arm64/,按目标架构
│       ├── duckdb/                         # DuckDB CLI 压缩包
│       ├── extensions/                     # DuckDB 扩展文件
│       └── oracle/                         # Oracle Instant Client 压缩包
├── bin/
│   └── duckdb                              # DuckDB CLI
├── runtime/
│   ├── extensions/
│   │   ├── odbc_scanner.duckdb_extension   # DuckDB ODBC 扩展
│   │   └── postgres_scanner.duckdb_extension # DuckDB PostgreSQL 扩展
│   ├── oracle/
│   │   └── instantclient_23_26/            # Oracle Instant Client
│   │       ├── libsqora.so.23.1           # ODBC 驱动
│   │       ├── libclntsh.so.23.1           # Oracle 客户端库
│   │       └── ...
│   └── unixodbc/
│       └── lib/
│           ├── libodbc.so.2                # unixODBC 运行库
│           ├── libodbcinst.so.2            # unixODBC 驱动管理
│           ├── libltdl.so.7                # libtool (unixODBC 依赖)
│           └── libaio.so.1                 # libaio (Oracle 依赖)
└── var/
    ├── odbc/
    │   ├── odbcinst.ini                    # ODBC 驱动配置
    │   └── odbc.ini                        # ODBC 数据源配置
    ├── home/                               # DuckDB HOME 目录
    └── tmp/                                # 临时文件目录

一、安装部署

步骤 1: 创建项目目录

mkdir -p /data/duckdb-oracle-gaussdb
cd /data/duckdb-oracle-gaussdb

步骤 2: 创建安装脚本

创建 install.sh 文件:

#!/usr/bin/env bash
set -euo pipefail

# ============================================================
#  DuckDB + Oracle + GaussDB 自包含安装脚本
#  支持: Debian/Ubuntu + Fedora/RHEL, amd64 + arm64
# ============================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$SCRIPT_DIR"
DOWNLOAD_DIR="$PROJECT_DIR/.downloads"
RUNTIME_DIR="$PROJECT_DIR/runtime"
BIN_DIR="$PROJECT_DIR/bin"
VAR_DIR="$PROJECT_DIR/var"
EXT_DIR="$RUNTIME_DIR/extensions"
ORACLE_DIR="$RUNTIME_DIR/oracle"
UNIXODBC_DIR="$RUNTIME_DIR/unixodbc/lib"

DUCKDB_VERSION="1.5.3"

# ---------- 颜色输出 ----------
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
info()  { echo -e "${GREEN}[INFO]${NC}  $*"; }
warn()  { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }

# ---------- 检测 OS 和架构 ----------
detect_env() {
    if [[ -f /etc/os-release ]]; then
        . /etc/os-release
        case "$ID" in
            ubuntu|debian|linuxmint|pop) OS_FAMILY="debian" ;;
            fedora)                        OS_FAMILY="fedora" ;;
            rhel|rocky|almalinux|centos|kylin|openeuler|anolis)   OS_FAMILY="rhel" ;;
            *) error "不支持的发行版: $ID";;
        esac
    else
        error "无法检测操作系统"
    fi

    ARCH="$(uname -m)"
    case "$ARCH" in
        x86_64)  ARCH_SUFFIX="amd64";  ORACLE_ARCH="linuxx64"  ;;
        aarch64) ARCH_SUFFIX="arm64";  ORACLE_ARCH="linuxarm64" ;;
        *) error "不支持的架构: $ARCH";;
    esac

    info "检测到: OS=$OS_FAMILY ($ID $VERSION_ID), 架构=$ARCH ($ARCH_SUFFIX)"
}

# ---------- 安装系统依赖 ----------
install_sys_deps() {
    info "安装系统依赖..."
    if [[ "$OS_FAMILY" == "debian" ]]; then
        export DEBIAN_FRONTEND=noninteractive
        apt-get update -qq
        # Debian 12+ 用 libaio1t64, 旧版用 libaio1
        apt-get install -y -qq curl unzip libaio1t64 unixodbc 2>/dev/null || \
        apt-get install -y -qq curl unzip libaio1 unixodbc 2>/dev/null || \
        apt-get install -y -qq curl unzip libaio unixodbc
    elif [[ "$OS_FAMILY" == "fedora" || "$OS_FAMILY" == "rhel" ]]; then
        dnf install -y -q curl unzip libaio unixODBC 2>/dev/null || \
        yum install -y -q curl unzip libaio unixODBC
    fi
    info "系统依赖安装完成"
}

# ---------- 下载文件 (带代理备选) ----------
download() {
    local url="$1" dest="$2"
    if [[ -f "$dest" ]]; then
        info "已存在,跳过: $(basename "$dest")"
        return
    fi
    info "下载: $(basename "$dest")"
    if curl -L --fail --connect-timeout 30 --max-time 300 --retry 2 \
         -o "$dest" "$url" 2>/dev/null; then
        return
    fi
    warn "直连失败, 尝试通过代理下载..."
    local proxy_url="https://ghfast.top/${url}"
    if curl -L --fail --connect-timeout 30 --max-time 300 \
         -o "$dest" "$proxy_url" 2>/dev/null; then
        return
    fi
    error "下载失败: $url"
}

# ---------- 下载 DuckDB CLI ----------
install_duckdb() {
    info "安装 DuckDB CLI v${DUCKDB_VERSION} ($ARCH_SUFFIX)..."
    mkdir -p "$BIN_DIR"
    local zip_url
    zip_url="https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/duckdb_cli-linux-${ARCH_SUFFIX}.zip"
    local zip_file="$DOWNLOAD_DIR/duckdb_cli-linux-${ARCH_SUFFIX}.zip"

    download "$zip_url" "$zip_file"
    unzip -o "$zip_file" -d "$DOWNLOAD_DIR/duckdb-tmp" > /dev/null
    cp -f "$DOWNLOAD_DIR/duckdb-tmp/duckdb" "$BIN_DIR/duckdb"
    chmod +x "$BIN_DIR/duckdb"
    rm -rf "$DOWNLOAD_DIR/duckdb-tmp"

    info "DuckDB CLI: $($BIN_DIR/duckdb --version 2>/dev/null | head -1)"
}

# ---------- 下载 DuckDB 扩展 ----------
install_extensions() {
    info "下载 DuckDB 扩展 ($ARCH_SUFFIX)..."
    mkdir -p "$EXT_DIR"
    mkdir -p "$VAR_DIR/home"

    for ext_name in odbc postgres; do
        local ext_file="$EXT_DIR/${ext_name}_scanner.duckdb_extension"
        if [[ -f "$ext_file" ]]; then
            info "已存在,跳过: ${ext_name}_scanner"
            continue
        fi

        # 先尝试直接从扩展 CDN 下载
        local ext_base
        ext_base="https://extensions.duckdb.org/v${DUCKDB_VERSION}/linux_${ARCH_SUFFIX}"
        if curl -sL --fail --connect-timeout 30 \
             -o "$ext_file" \
             "${ext_base}/${ext_name}_scanner.duckdb_extension" 2>/dev/null; then
            info "直接下载成功: ${ext_name}_scanner"
            continue
        fi

        # 通过 DuckDB 内置 INSTALL 下载
        info "通过 DuckDB INSTALL 下载: ${ext_name}"
        HOME="$VAR_DIR/home" "$BIN_DIR/duckdb" -unsigned \
            -c "INSTALL ${ext_name};" 2>/dev/null || true

        local cached
        cached="$(find "$VAR_DIR/home/.duckdb/extensions/" \
            -name "${ext_name}_scanner.duckdb_extension" 2>/dev/null | head -1)"
        if [[ -n "$cached" ]]; then
            cp -f "$cached" "$ext_file"
            info "下载成功: ${ext_name}_scanner"
        else
            error "无法下载 ${ext_name}_scanner 扩展"
        fi
    done

    info "扩展文件:"
    ls -lh "$EXT_DIR/"
}

# ---------- 安装 Oracle Instant Client ----------
install_oracle_client() {
    info "安装 Oracle Instant Client ($ORACLE_ARCH)..."
    mkdir -p "$ORACLE_DIR"

    local base_url
    base_url="https://download.oracle.com/otn_software/linux/instantclient"

    download "${base_url}/instantclient-basiclite-${ORACLE_ARCH}.zip" \
        "$DOWNLOAD_DIR/ic-basiclite.zip"
    download "${base_url}/instantclient-odbc-${ORACLE_ARCH}.zip" \
        "$DOWNLOAD_DIR/ic-odbc.zip"

    unzip -o "$DOWNLOAD_DIR/ic-basiclite.zip" -d "$ORACLE_DIR" > /dev/null
    unzip -o "$DOWNLOAD_DIR/ic-odbc.zip" -d "$ORACLE_DIR" > /dev/null

    ORACLE_IC_DIR="$(find "$ORACLE_DIR" -maxdepth 1 -type d \
        -name 'instantclient_*' | head -1)"
    if [[ -z "$ORACLE_IC_DIR" ]]; then
        error "Oracle Instant Client 解压后未找到 instantclient_* 目录"
    fi

    local sqora
    sqora="$(find "$ORACLE_IC_DIR" -name 'libsqora.so.*' | head -1)"
    if [[ -n "$sqora" ]]; then
        info "找到 Oracle ODBC 驱动: $(basename "$sqora")"
    else
        error "未找到 libsqora.so (Oracle ODBC 驱动)"
    fi

    info "Oracle Instant Client: $ORACLE_IC_DIR"
}

# ---------- 提取 unixODBC 运行时库 ----------
extract_unixodbc() {
    info "提取 unixODBC 运行时库到项目目录..."
    mkdir -p "$UNIXODBC_DIR"

    local lib_search_paths=()
    if [[ "$OS_FAMILY" == "debian" ]]; then
        lib_search_paths=(
            /usr/lib/x86_64-linux-gnu
            /usr/lib/aarch64-linux-gnu
            /usr/lib
        )
    elif [[ "$OS_FAMILY" == "fedora" || "$OS_FAMILY" == "rhel" ]]; then
        lib_search_paths=(/usr/lib64 /usr/lib)
    fi

    for lib in libodbc.so.2 libodbcinst.so.2 libodbc.so.2.0.0 \
               libodbcinst.so.2.0.0; do
        for sp in "${lib_search_paths[@]}"; do
            if [[ -f "$sp/$lib" ]]; then
                cp -a "$sp/$lib" "$UNIXODBC_DIR/" 2>/dev/null || true
                break
            fi
        done
    done

    # libaio + libltdl (含符号链接)
    for lib_pattern in "libaio.so.1*" "libltdl.so.7*"; do
        for sp in "${lib_search_paths[@]}"; do
            local found
            found="$(find "$sp" -maxdepth 1 -name "$lib_pattern" 2>/dev/null | head -1)"
            if [[ -n "$found" ]]; then
                cp -a "$sp/$lib_pattern" "$UNIXODBC_DIR/" 2>/dev/null || true
                break
            fi
        done
    done

    info "unixODBC 运行时库:"
    ls -lh "$UNIXODBC_DIR/"
}

# ---------- 生成 ODBC 配置 ----------
generate_odbc_config() {
    info "生成 ODBC 配置..."
    mkdir -p "$VAR_DIR/odbc" "$VAR_DIR/home" "$VAR_DIR/tmp"

    local sqora_file
    sqora_file="$(find "$ORACLE_IC_DIR" -name 'libsqora.so.*' \
        ! -name '*.so' | sort -V | tail -1)"
    if [[ -z "$sqora_file" ]]; then
        error "未找到 Oracle ODBC 驱动库"
    fi

    cat >"$VAR_DIR/odbc/odbcinst.ini" <<EOF
[Oracle ODBC driver]
Description=Oracle ODBC driver (self-contained)
Driver=${sqora_file}
Setup=
FileUsage=1
CPTimeout=
CPReuse=
EOF

    cat >"$VAR_DIR/odbc/odbc.ini" <<EOF
[OraclePDB1]
Driver=Oracle ODBC driver
Description=Oracle PDB1 Self-Contained
DBQ=//192.168.163.227:1527/pdb1
UserID=system

[ODBC Data Sources]
OraclePDB1=Oracle ODBC driver
EOF

    info "ODBC 配置文件已生成"
}

# ---------- 写入 env.sh ----------
write_env_file() {
    cat >"$PROJECT_DIR/env.sh" <<EOF
# 此文件由 install.sh 自动生成,请勿手动编辑

export PROJECT_DIR="$PROJECT_DIR"
export DUCKDB_BIN="$PROJECT_DIR/bin/duckdb"
export ODBC_EXT="$PROJECT_DIR/runtime/extensions/odbc_scanner.duckdb_extension"
export POSTGRES_EXT="$PROJECT_DIR/runtime/extensions/postgres_scanner.duckdb_extension"
export ORACLE_IC_DIR="$ORACLE_IC_DIR"
export UNIXODBC_LIB_DIR="$UNIXODBC_DIR"
export ODBC_CONF_DIR="$PROJECT_DIR/var/odbc"
export TEMP_DIR="$PROJECT_DIR/var/tmp"
export HOME_DIR="$PROJECT_DIR/var/home"
export ORACLE_DRIVER_NAME="Oracle ODBC driver"
EOF

    info "env.sh 已生成"
}

# ---------- 验证安装 ----------
verify_installation() {
    echo ""
    info "========== 安装验证 =========="
    local errors=0

    if [[ -x "$BIN_DIR/duckdb" ]]; then
        info "[OK] DuckDB CLI: $($BIN_DIR/duckdb --version 2>/dev/null | head -1)"
    else error "[FAIL] DuckDB CLI"; errors=$((errors+1)); fi

    if [[ -f "$EXT_DIR/odbc_scanner.duckdb_extension" ]]; then
        info "[OK] ODBC 扩展"
    else error "[FAIL] ODBC 扩展"; errors=$((errors+1)); fi

    if [[ -f "$EXT_DIR/postgres_scanner.duckdb_extension" ]]; then
        info "[OK] PostgreSQL 扩展"
    else error "[FAIL] PostgreSQL 扩展"; errors=$((errors+1)); fi

    if [[ -d "$ORACLE_IC_DIR" ]]; then
        info "[OK] Oracle Instant Client: $ORACLE_IC_DIR"
    else error "[FAIL] Oracle Instant Client"; errors=$((errors+1)); fi

    if ls "$UNIXODBC_DIR"/libodbc.so* 1>/dev/null 2>&1; then
        info "[OK] unixODBC 库文件"
    else error "[FAIL] unixODBC 库"; errors=$((errors+1)); fi

    if [[ $errors -gt 0 ]]; then error "安装失败: $errors 项错误"; fi

    info "========== 安装完成 =========="
    echo ""
    info "下一步: cd $PROJECT_DIR && ./run.sh"
}

# ========== 主流程 ==========
main() {
    echo "============================================================"
    echo "  DuckDB + Oracle + GaussDB 自包含安装"
    echo "  OS: $(. /etc/os-release 2>/dev/null && echo "$ID $VERSION_ID")  架构: $(uname -m)"
    echo "============================================================"
    echo ""

    mkdir -p "$DOWNLOAD_DIR" "$RUNTIME_DIR" "$VAR_DIR"

    detect_env
    install_sys_deps
    install_duckdb
    install_extensions
    install_oracle_client
    extract_unixodbc
    generate_odbc_config
    write_env_file
    verify_installation
}

main "$@"

赋予执行权限并运行:

chmod +x install.sh
./install.sh

安装过程会自动:

  1. 检测操作系统和 CPU 架构
  2. 安装系统依赖(curl、unzip、libaio、unixODBC)
  3. 下载 DuckDB CLI v1.5.3
  4. 下载 DuckDB 的 ODBC 和 PostgreSQL 扩展
  5. 下载 Oracle Instant Client(Basic Lite + ODBC)
  6. 提取 unixODBC 运行时库到项目目录
  7. 生成 ODBC 配置文件
  8. 验证所有组件

预期安装输出:

============================================================
  DuckDB + Oracle + GaussDB 自包含安装
  OS: ubuntu 22.04  架构: x86_64
============================================================

[INFO]  检测到: OS=debian (ubuntu 22.04), 架构=x86_64 (amd64)
[INFO]  安装系统依赖...
[INFO]  系统依赖安装完成
[INFO]  安装 DuckDB CLI v1.5.3 (amd64)...
[INFO]  DuckDB CLI: v1.5.3 (Variegata) 14eca11bd9
[INFO]  下载 DuckDB 扩展 (amd64)...
...
[INFO]  ========== 安装完成 ==========
[INFO]  下一步: cd /data/duckdb-oracle-gaussdb && ./run.sh

二、运行跨库查询

步骤 3: 创建运行脚本

创建 run.sh 文件:

#!/usr/bin/env bash
set -euo pipefail

# ============================================================
#  DuckDB 跨库查询: Oracle + GaussDB
#  通过一条 SQL 同时查询两个异构数据库
# ============================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

if [[ ! -f "$SCRIPT_DIR/env.sh" ]]; then
    echo "[ERROR] env.sh 未找到,请先执行: $SCRIPT_DIR/install.sh" >&2
    exit 1
fi

source "$SCRIPT_DIR/env.sh"

# ---------- 默认连接参数 (可通过环境变量覆盖) ----------
ORACLE_HOST="${ORACLE_HOST:-192.168.163.227}"
ORACLE_PORT="${ORACLE_PORT:-1527}"
ORACLE_SERVICE="${ORACLE_SERVICE:-pdb1}"
ORACLE_USER="${ORACLE_USER:-system}"
ORACLE_PASSWORD="${ORACLE_PASSWORD:-oraclepassword}"

GAUSSDB_HOST="${GAUSSDB_HOST:-192.168.163.227}"
GAUSSDB_PORT="${GAUSSDB_PORT:-15432}"
GAUSSDB_DB="${GAUSSDB_DB:-postgres}"
GAUSSDB_USER="${GAUSSDB_USER:-admin}"
GAUSSDB_PASSWORD="${GAUSSDB_PASSWORD:-gaussdbpassword}"

# ---------- 校验 ----------
for f in "$DUCKDB_BIN" "$ODBC_EXT" "$POSTGRES_EXT"; do
    if [[ ! -f "$f" ]]; then
        echo "[ERROR] 文件未找到: $f" >&2
        exit 1
    fi
done

# ---------- 设置运行环境 ----------
export HOME="$HOME_DIR"
export ODBCSYSINI="$ODBC_CONF_DIR"
export ODBCINI="$ODBC_CONF_DIR/odbc.ini"
export LD_LIBRARY_PATH="$ORACLE_IC_DIR:$UNIXODBC_LIB_DIR:${LD_LIBRARY_PATH:-}"

# ---------- 转义函数 ----------
escape_sql() {
    printf "%s" "$1" | sed "s/'/''/g"
}

ORACLE_CONN_STR="Driver={$ORACLE_DRIVER_NAME};DBQ=//$ORACLE_HOST:$ORACLE_PORT/$ORACLE_SERVICE"
ORACLE_CONN_ESC="$(escape_sql "$ORACLE_CONN_STR")"
ORACLE_USER_ESC="$(escape_sql "$ORACLE_USER")"
ORACLE_PASS_ESC="$(escape_sql "$ORACLE_PASSWORD")"

GAUSSDB_CONN_STR="host=$GAUSSDB_HOST port=$GAUSSDB_PORT dbname=$GAUSSDB_DB user=$GAUSSDB_USER password=$GAUSSDB_PASSWORD sslmode=disable"
GAUSSDB_CONN_ESC="$(escape_sql "$GAUSSDB_CONN_STR")"

# ---------- DuckDB SQL ----------
cat <<'SQL_EOF' > "$TEMP_DIR/query.sql"
-- =============================================================
-- DuckDB 跨库联合查询: Oracle + GaussDB
-- =============================================================

-- 加载扩展
LOAD '__ODBC_EXT__';
LOAD '__POSTGRES_EXT__';

-- GaussDB 必须设置 text 协议
SET pg_use_text_protocol=true;
SET threads=1;

-- 连接 Oracle (通过 ODBC)
SET VARIABLE oracle_conn = odbc_connect(
    '__ORACLE_CONN__',
    '__ORACLE_USER__',
    '__ORACLE_PASS__'
);

-- 连接 GaussDB (通过 PostgreSQL Scanner)
ATTACH '__GAUSSDB_CONN__' AS gaussdb (TYPE postgres);

-- =============================================================
-- 测试 1: 分别验证两个数据库连通性
-- =============================================================
SELECT '===== [1] 测试 Oracle 连通性 =====' AS info;
SELECT * FROM odbc_query(getvariable('oracle_conn'),
    'SELECT ''Oracle_OK'' AS status, SYSDATE AS oracle_time FROM dual');

SELECT '===== [2] 测试 GaussDB 连通性 =====' AS info;
SELECT 'GaussDB_OK' AS status, current_timestamp AS gaussdb_time
FROM postgres_query('gaussdb', 'SELECT current_database()');

-- =============================================================
-- 测试 2: 一条 SQL 同时查询 Oracle 和 GaussDB
-- =============================================================
SELECT '===== [3] 跨库联合查询 =====' AS info;

SELECT
    o.status            AS oracle_result,
    o.oracle_time       AS oracle_sysdate,
    g.status            AS gaussdb_result,
    g.gaussdb_time      AS gaussdb_timestamp
FROM
    (SELECT status, oracle_time
     FROM odbc_query(getvariable('oracle_conn'),
       'SELECT ''Oracle_OK'' AS status, TO_CHAR(SYSDATE, ''YYYY-MM-DD HH24:MI:SS'') AS oracle_time FROM dual')
    ) o
CROSS JOIN
    (SELECT 'GaussDB_OK' AS status, current_timestamp AS gaussdb_time
     FROM postgres_query('gaussdb', 'SELECT current_timestamp AS ts')
    ) g;

-- =============================================================
-- 测试 3: 获取两个数据库的版本信息
-- =============================================================
SELECT '===== [4] 数据库版本信息 =====' AS info;

SELECT 'Oracle' AS db_source, version_str AS version
FROM odbc_query(getvariable('oracle_conn'),
  'SELECT banner AS version_str FROM v$version WHERE rownum = 1')
UNION ALL
SELECT 'GaussDB' AS db_source, setting AS version
FROM gaussdb.pg_catalog.pg_settings WHERE name = 'server_version';

-- =============================================================
-- 清理
-- =============================================================
SELECT odbc_close(getvariable('oracle_conn'));
DETACH gaussdb;

SELECT '===== 跨库查询完成 =====' AS info;
SQL_EOF

# 替换 SQL 中的占位符
sed -i "s|__ODBC_EXT__|${ODBC_EXT}|g" "$TEMP_DIR/query.sql"
sed -i "s|__POSTGRES_EXT__|${POSTGRES_EXT}|g" "$TEMP_DIR/query.sql"
sed -i "s|__ORACLE_CONN__|${ORACLE_CONN_ESC}|g" "$TEMP_DIR/query.sql"
sed -i "s|__ORACLE_USER__|${ORACLE_USER_ESC}|g" "$TEMP_DIR/query.sql"
sed -i "s|__ORACLE_PASS__|${ORACLE_PASS_ESC}|g" "$TEMP_DIR/query.sql"
sed -i "s|__GAUSSDB_CONN__|${GAUSSDB_CONN_ESC}|g" "$TEMP_DIR/query.sql"

# ---------- 执行 ----------
echo "============================================================"
echo "  DuckDB 跨库查询: Oracle + GaussDB"
echo "  Oracle:  ${ORACLE_USER}@${ORACLE_HOST}:${ORACLE_PORT}/${ORACLE_SERVICE}"
echo "  GaussDB: ${GAUSSDB_USER}@${GAUSSDB_HOST}:${GAUSSDB_PORT}/${GAUSSDB_DB}"
echo "============================================================"
echo ""

"$DUCKDB_BIN" -batch -unsigned < "$TEMP_DIR/query.sql"

赋予执行权限并运行:

chmod +x run.sh
./run.sh

实际运行结果

============================================================
  DuckDB 跨库查询: Oracle + GaussDB
  Oracle:  system@192.168.163.227:1527/pdb1
  GaussDB: admin@192.168.163.227:15432/postgres
============================================================

┌────────────────────────────────────┐
│                info                │
│              varchar               │
├────────────────────────────────────┤
│ ===== [1] 测试 Oracle 连通性 ===== │
└────────────────────────────────────┘
┌───────────┬─────────────┐
│  STATUS   │ ORACLE_TIME │
│  varchar  │    date     │
├───────────┼─────────────┤
│ Oracle_OK │ 2026-05-22  │
└───────────┴─────────────┘
┌─────────────────────────────────────┐
│                info                 │
│               varchar               │
├─────────────────────────────────────┤
│ ===== [2] 测试 GaussDB 连通性 ===== │
└─────────────────────────────────────┘
┌────────────┬───────────────────────────────┐
│   status   │         gaussdb_time          │
│  varchar   │   timestamp with time zone    │
├────────────┼───────────────────────────────┤
│ GaussDB_OK │ 2026-05-22 16:26:36.448552+08 │
└────────────┴───────────────────────────────┘
┌──────────────────────────────┐
│             info             │
│           varchar            │
├──────────────────────────────┤
│ ===== [3] 跨库联合查询 ===== │
└──────────────────────────────┘
┌───────────────┬─────────────────────┬────────────────┬───────────────────────────────┐
│ oracle_result │   oracle_sysdate    │ gaussdb_result │       gaussdb_timestamp       │
│    varchar    │       varchar       │    varchar     │   timestamp with time zone    │
├───────────────┼─────────────────────┼────────────────┼───────────────────────────────┤
│ Oracle_OK     │ 2026-05-22 07:09:31 │ GaussDB_OK     │ 2026-05-22 07:09:34.067699+00 │
└───────────────┴─────────────────────┴────────────────┴───────────────────────────────┘
┌────────────────────────────────┐
│              info              │
│            varchar             │
├────────────────────────────────┤
│ ===== [4] 数据库版本信息 ===== │
└────────────────────────────────┘
┌───────────┬────────────────────────────────────────────────────────────────────────┐
│ db_source │                                version                                 │
│  varchar  │                                varchar                                 │
├───────────┼────────────────────────────────────────────────────────────────────────┤
│ Oracle    │ Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production │
│ GaussDB   │ gaussdb (GaussDB Kernel 506.0.0.SPC0500 build d474632c) compiled at... │
└───────────┴────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────┐
│           info           │
│         varchar          │
├──────────────────────────┤
│ ===== 跨库查询完成 ===== │
└──────────────────────────┘

可以看到:

  • 测试 1: Oracle 单独查询 DUAL 表成功
  • 测试 2: GaussDB 单独查询成功
  • 测试 3: 一条 SQL 同时从 Oracle 和 GaussDB 获取数据并做 CROSS JOIN 联合展示
  • 测试 4: 分别获取了 Oracle 19c 和 GaussDB 的版本信息

三、关键代码解析

3.1 DuckDB 连接 Oracle 的方式

LOAD 'runtime/extensions/odbc_scanner.duckdb_extension';
SET VARIABLE oracle_conn = odbc_connect(
    'Driver={Oracle ODBC driver};DBQ=//192.168.163.227:1527/pdb1',
    'system',
    'oraclepassword'
);

使用 DuckDB 的 odbc_connect() 建立连接,返回一个连接句柄存入变量。之后通过 odbc_query(连接句柄, SQL) 执行任意 Oracle SQL。

3.2 DuckDB 连接 GaussDB 的方式

LOAD 'runtime/extensions/postgres_scanner.duckdb_extension';
SET pg_use_text_protocol=true;  -- 关键! GaussDB 需要此设置
ATTACH 'host=192.168.163.227 port=15432 dbname=postgres user=admin password=gaussdbpassword sslmode=disable'
  AS gaussdb (TYPE postgres);

使用 ATTACH 语法将 GaussDB 作为外部数据库挂载到 DuckDB 中,之后可以直接用 gaussdb.schema.table 的方式查询。

重要: SET pg_use_text_protocol=true; 是连接 GaussDB 的关键设置。

此外,DuckDB 还提供了 postgres_query() 函数,可以在 GaussDB 服务端直接执行 SQL 并返回结果。这在需要调用 GaussDB 特有函数(如 version())时非常有用,因为 DuckDB 的 ATTACH 模式下同名函数会被 DuckDB 自身拦截:

-- 错误: version() 被 DuckDB 拦截,返回的是 DuckDB 版本
SELECT version() FROM gaussdb.pg_catalog.pg_settings LIMIT 1;
-- 结果: v1.5.3 (错误!)

-- 正确: 用 postgres_query 在 GaussDB 端执行,返回真实版本
SELECT * FROM postgres_query('gaussdb', 'SELECT version()');
-- 结果: gaussdb (GaussDB Kernel 506.0.0.SPC0500 build d474632c) compiled at 2025-08-28 13:06:27 last mr 24872 release
```GaussDB 虽然兼容 PostgreSQL 协议,但在二进制协议传输上可能存在兼容性问题,使用 text 协议可以避免这类问题。

### 3.3 跨库联合查询的核心 SQL

```sql
SELECT
    o.status        AS oracle_result,
    o.oracle_time   AS oracle_sysdate,
    g.status        AS gaussdb_result,
    g.gaussdb_time  AS gaussdb_timestamp
FROM
    (SELECT status, oracle_time
     FROM odbc_query(getvariable('oracle_conn'),
       'SELECT ''Oracle_OK'' AS status,
               TO_CHAR(SYSDATE, ''YYYY-MM-DD HH24:MI:SS'') AS oracle_time
        FROM dual')
    ) o
CROSS JOIN
    (SELECT 'GaussDB_OK' AS status, current_timestamp AS gaussdb_time
     FROM postgres_query('gaussdb', 'SELECT current_timestamp AS ts')
    ) g;

这条 SQL 的核心逻辑:

  1. 通过 odbc_query() 从 Oracle 查询 DUAL 表获取 SYSDATE
  2. 通过 DuckDB 本地查询获取 GaussDB 的时间戳
  3. 使用 CROSS JOIN 将两个结果合并到一行输出

3.4 自包含部署的关键环境变量

export HOME="$HOME_DIR"                          # 隔离 DuckDB 扩展缓存目录
export ODBCSYSINI="$ODBC_CONF_DIR"               # 指定 ODBC 驱动配置位置
export ODBCINI="$ODBC_CONF_DIR/odbc.ini"         # 指定 ODBC 数据源配置位置
export LD_LIBRARY_PATH="$ORACLE_IC_DIR:$UNIXODBC_LIB_DIR:${LD_LIBRARY_PATH:-}"

这些环境变量确保:

  • DuckDB 不会去 ~/.duckdb/extensions 寻找扩展
  • ODBC 驱动管理器使用项目内的配置文件,而非 /etc/odbc*.ini
  • 动态链接器能找到 Oracle 客户端库和 unixODBC 运行库

四、自定义使用

修改数据库连接参数

可以通过环境变量覆盖默认连接参数:

ORACLE_HOST=10.0.0.1 \
ORACLE_PORT=1521 \
ORACLE_SERVICE=orcl \
ORACLE_USER=scott \
ORACLE_PASSWORD=tiger \
GAUSSDB_HOST=10.0.0.2 \
GAUSSDB_PORT=5432 \
GAUSSDB_DB=mydb \
GAUSSDB_USER=myuser \
GAUSSDB_PASSWORD=mypass \
./run.sh

自定义查询 SQL

编辑 run.sh 中的 SQL 模板(cat <<'SQL_EOF'SQL_EOF 之间的部分),替换为你自己的查询语句。

例如,同时从 Oracle 的 EMP 表和 GaussDB 的 employee 表查询数据:

-- Oracle 中的数据
SELECT * FROM odbc_query(getvariable('oracle_conn'),
    'SELECT empno, ename, sal FROM emp WHERE deptno = 10');

-- GaussDB 中的数据 (通过 ATTACH 挂载后, 使用 gaussdb.schema.table 查询)
SELECT * FROM gaussdb.public.employee WHERE department_id = 10;

-- 跨库联合
SELECT 'oracle' AS source, empno AS id, ename AS name, sal AS salary
FROM odbc_query(getvariable('oracle_conn'),
    'SELECT empno, ename, sal FROM emp')
UNION ALL
SELECT 'gaussdb' AS source, id, name, salary
FROM gaussdb.public.employee;

五、常见问题排查

Q1: odbc_connect 报错 Could not locate or load driver

原因: Oracle ODBC 驱动库(libsqora.so)未找到。

排查:

# 检查驱动文件是否存在
find runtime/oracle -name 'libsqora.so*'

# 检查 ODBC 配置中的 Driver 路径是否正确
cat var/odbc/odbcinst.ini

# 检查 LD_LIBRARY_PATH
echo $LD_LIBRARY_PATH

Q2: odbc_connect 报错 ORA-12154: TNS:could not resolve the connect identifier

原因: 连接字符串格式错误,或数据库地址/端口/服务名不正确。

排查: 确认 DBQ 参数格式为 //host:port/service_name

Q3: GaussDB 连接报错 FATAL: ...

原因: GaussDB 连接参数不正确,或网络不通。

排查:

# 测试网络连通性
nc -zv 192.168.163.227 15432

# 确认密码中的特殊字符已正确转义

注意: 如果密码中包含 @ 等特殊字符,在 PostgreSQL 连接字符串中需要正确处理。在 run.sh 的连接字符串中,密码通过 escape_sql 函数处理了单引号转义,但如果密码中包含其他特殊字符可能需要额外处理。

Q4: INSTALL 扩展失败

原因: 网络问题导致 DuckDB 无法从官方 CDN 下载扩展。

解决: 可以手动下载扩展文件放到 runtime/extensions/ 目录:

  1. 访问 https://extensions.duckdb.org/v1.5.3/linux_amd64/ 查看可用扩展
  2. 下载 odbc_scanner.duckdb_extensionpostgres_scanner.duckdb_extension
  3. 放到 runtime/extensions/ 目录

或者使用代理:

curl -L -o runtime/extensions/postgres_scanner.duckdb_extension \
  "https://ghfast.top/https://extensions.duckdb.org/v1.5.3/linux_amd64/postgres_scanner.duckdb_extension"

Q5: libaio.so.1: cannot open shared object file

原因: 缺少 libaio 库。Oracle Instant Client 依赖此库。

解决:

# Debian/Ubuntu
apt-get install libaio1t64   # Debian 12+
# 或
apt-get install libaio1       # 旧版

# Fedora/RHEL
dnf install libaio

六、离线部署方案

生产环境通常处于内网,无法直接访问外网下载 DuckDB、扩展和 Oracle 客户端。本方案提供完整的离线部署流程:在有网络的机器上预下载所有依赖,打包后拷贝到内网目标机器安装

离线部署流程概览

┌──────────────────────┐         ┌──────────────────────┐
│  有网络的机器         │         │  内网目标机器         │
│                      │         │                      │
│  1. ./prepare.sh     │  scp/   │  3. 解压离线包        │
│     (下载所有依赖)    │  U盘/   │  4. ./install_offline.sh│
│  2. 打包离线包        │  其他   │     (从离线包安装)    │
│     tar czf ...      ───────→ │  5. ./run.sh          │
└──────────────────────┘         └──────────────────────┘

前提条件

  • 有网络机器: 能访问 GitHub、DuckDB 扩展 CDN、Oracle 官网
  • 内网目标机器: 有 yum 或 apt 软件源(用于安装 curl、unzip、libaio、unixODBC 这四个基础系统包),有 root 权限
  • 两台机器 CPU 架构一致(amd64 打包的不能在 arm64 上用,反之亦然)

步骤 1: 在有网络的机器上准备离线包

准备脚本提供两个版本:Linux 版prepare.sh,bash)和 Windows 版prepare.ps1,PowerShell)。两者功能完全相同,根据你手上有的网络机器选择即可。

方式 A: 在 Linux 上准备

prepare.sh 脚本放到项目目录下:

mkdir -p /data/duckdb-oracle-gaussdb
cd /data/duckdb-oracle-gaussdb

创建 prepare.sh 文件:

#!/usr/bin/env bash
set -euo pipefail

# ============================================================
#  离线部署 - 准备脚本
#  在有网络的机器上运行,下载所有离线依赖到 offline_packages/ 目录
#
#  用法:
#    ./prepare.sh                    # 下载当前架构的包
#    ./prepare.sh --all-arch         # 下载 amd64 + arm64 全部包
#    ARCH=arm64 ./prepare.sh         # 指定下载 arm64 的包
# ============================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OFFLINE_DIR="$SCRIPT_DIR/offline_packages"
DUCKDB_VERSION="1.5.3"

# ---------- 颜色输出 ----------
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
info()  { echo -e "${GREEN}[INFO]${NC}  $*"; }
warn()  { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }

# ---------- 下载函数 ----------
download() {
    local url="$1" dest="$2" desc="$3"
    if [[ -f "$dest" ]]; then
        info "已存在,跳过: $desc"
        return
    fi
    info "下载: $desc"
    if curl -L --fail --connect-timeout 30 --max-time 600 --retry 3 \
         -o "$dest" "$url" 2>/dev/null; then
        return
    fi
    warn "直连失败, 尝试代理: $desc"
    local proxy_url="https://ghfast.top/${url}"
    if curl -L --fail --connect-timeout 30 --max-time 600 \
         -o "$dest" "$proxy_url" 2>/dev/null; then
        return
    fi
    error "下载失败: $desc ($url)"
}

# ---------- 下载单个架构 ----------
prepare_single_arch() {
    local arch="$1"
    local arch_suffix="$2"
    local oracle_arch="$3"

    local arch_dir="$OFFLINE_DIR/$arch_suffix"
    mkdir -p "$arch_dir/duckdb" "$arch_dir/extensions" "$arch_dir/oracle"

    # 1. DuckDB CLI
    info "===== 下载 DuckDB CLI ($arch_suffix) ====="
    local duckdb_zip="duckdb_cli-linux-${arch_suffix}.zip"
    download \
        "https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/${duckdb_zip}" \
        "$arch_dir/duckdb/${duckdb_zip}" \
        "DuckDB CLI v${DUCKDB_VERSION} ($arch_suffix)"

    # 2. DuckDB 扩展
    info "===== 下载 DuckDB 扩展 ($arch_suffix) ====="
    local ext_base="https://extensions.duckdb.org/v${DUCKDB_VERSION}/linux_${arch_suffix}"
    for ext_name in odbc postgres; do
        local ext_file="${ext_name}_scanner.duckdb_extension"
        download \
            "${ext_base}/${ext_file}" \
            "$arch_dir/extensions/${ext_file}" \
            "DuckDB ${ext_name}_scanner 扩展 ($arch_suffix)"
    done

    # 3. Oracle Instant Client
    info "===== 下载 Oracle Instant Client ($oracle_arch) ====="
    local oracle_base="https://download.oracle.com/otn_software/linux/instantclient"
    download \
        "${oracle_base}/instantclient-basiclite-${oracle_arch}.zip" \
        "$arch_dir/oracle/instantclient-basiclite-${oracle_arch}.zip" \
        "Oracle Instant Client Basic Lite ($oracle_arch)"
    download \
        "${oracle_base}/instantclient-odbc-${oracle_arch}.zip" \
        "$arch_dir/oracle/instantclient-odbc-${oracle_arch}.zip" \
        "Oracle Instant Client ODBC ($oracle_arch)"

    echo ""
    info "$arch_suffix 包准备完成:"
    echo "  DuckDB CLI:    $(du -h "$arch_dir/duckdb/${duckdb_zip}" | cut -f1)"
    echo "  ODBC 扩展:     $(du -h "$arch_dir/extensions/odbc_scanner.duckdb_extension" | cut -f1)"
    echo "  PG 扩展:       $(du -h "$arch_dir/extensions/postgres_scanner.duckdb_extension" | cut -f1)"
    echo "  Oracle Basic:  $(du -h "$arch_dir/oracle/instantclient-basiclite-${oracle_arch}.zip" | cut -f1)"
    echo "  Oracle ODBC:   $(du -h "$arch_dir/oracle/instantclient-odbc-${oracle_arch}.zip" | cut -f1)"
}

# ========== 主流程 ==========
main() {
    echo "============================================================"
    echo "  离线部署准备 - 下载所有依赖包"
    echo "  DuckDB v${DUCKDB_VERSION}"
    echo "============================================================"
    echo ""

    mkdir -p "$OFFLINE_DIR"

    if [[ "${1:-}" == "--all-arch" ]]; then
        info "下载全部架构 (amd64 + arm64)"
        echo ""
        prepare_single_arch "amd64"  "amd64"  "linuxx64"
        echo ""
        prepare_single_arch "arm64"  "arm64"  "linuxarm64"
    else
        local target_arch="${ARCH:-}"
        if [[ -z "$target_arch" ]]; then
            local current_arch
            current_arch="$(uname -m)"
            case "$current_arch" in
                x86_64)  target_arch="amd64"  ;;
                aarch64) target_arch="arm64"  ;;
                *) error "不支持的架构: $current_arch" ;;
            esac
        fi
        case "$target_arch" in
            amd64)  prepare_single_arch "amd64" "amd64" "linuxx64" ;;
            arm64)  prepare_single_arch "arm64" "arm64" "linuxarm64" ;;
            *)      error "不支持的架构: $target_arch" ;;
        esac
    fi

    cat >"$OFFLINE_DIR/VERSION" <<EOF
DUCKDB_VERSION=${DUCKDB_VERSION}
PREPARE_DATE=$(date '+%Y-%m-%d %H:%M:%S')
PREPARE_HOST=$(hostname 2>/dev/null || echo unknown)
EOF

    echo ""
    info "============================================================"
    info "  离线包准备完成!"
    info "  目录: $OFFLINE_DIR"
    info ""
    info "  下一步:"
    info "  1. tar czf duckdb-offline-packages.tar.gz offline_packages/"
    info "  2. scp 到目标机器并解压"
    info "  3. 在目标机器执行: ./install_offline.sh"
    info "============================================================"
}

main "$@"

赋予执行权限并运行:

chmod +x prepare.sh

# 下载当前架构的包(根据机器自动判断 amd64/arm64)
./prepare.sh

# 如果需要同时准备两种架构的包(可在一台机器上为不同架构的目标机准备)
# ./prepare.sh --all-arch

方式 B: 在 Windows 上准备

如果你的网络机器是 Windows,使用 PowerShell 脚本。将以下内容保存为 prepare.ps1

param(
    [ValidateSet("amd64", "arm64")]
    [string]$Arch = "",
    [switch]$AllArch
)

$ErrorActionPreference = "Stop"
$DUCKDB_VERSION = "1.5.3"

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$OfflineDir = Join-Path $ScriptDir "offline_packages"

# ---------- 下载函数 ----------
function Download-File {
    param(
        [string]$Url,
        [string]$Dest,
        [string]$Desc
    )

    if (Test-Path $Dest) {
        Write-Host "[INFO]  已存在,跳过: $Desc" -ForegroundColor Green
        return
    }

    Write-Host "[INFO]  下载: $Desc" -ForegroundColor Green

    # 先尝试直连
    try {
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Invoke-WebRequest -Uri $Url -OutFile $Dest -UseBasicParsing `
            -TimeoutSec 600 -ErrorAction Stop
        return
    } catch {
        Write-Host "[WARN]  直连失败, 尝试代理..." -ForegroundColor Yellow
    }

    # 通过代理下载
    $proxyUrl = "https://ghfast.top/$Url"
    try {
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Invoke-WebRequest -Uri $proxyUrl -OutFile $Dest -UseBasicParsing `
            -TimeoutSec 600 -ErrorAction Stop
        return
    } catch {
        Write-Host "[ERROR] 下载失败: $Desc ($Url)" -ForegroundColor Red
        Write-Host "[ERROR] $($_.Exception.Message)" -ForegroundColor Red
        exit 1
    }
}

# ---------- 下载单个架构 ----------
function Prepare-SingleArch {
    param(
        [string]$ArchSuffix,
        [string]$OracleArch
    )

    $archDir = Join-Path $OfflineDir $ArchSuffix
    $duckdbDir = Join-Path $archDir "duckdb"
    $extDir = Join-Path $archDir "extensions"
    $oracleDir = Join-Path $archDir "oracle"

    New-Item -ItemType Directory -Force -Path $duckdbDir | Out-Null
    New-Item -ItemType Directory -Force -Path $extDir | Out-Null
    New-Item -ItemType Directory -Force -Path $oracleDir | Out-Null

    # 1. DuckDB CLI
    Write-Host ""
    Write-Host "===== 下载 DuckDB CLI ($ArchSuffix) ====="

    $duckdbZip = "duckdb_cli-linux-${ArchSuffix}.zip"
    $duckdbUrl = "https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/${duckdbZip}"
    $duckdbDest = Join-Path $duckdbDir $duckdbZip
    Download-File -Url $duckdbUrl -Dest $duckdbDest `
        -Desc "DuckDB CLI v${DUCKDB_VERSION} ($ArchSuffix)"

    # 2. DuckDB 扩展
    Write-Host ""
    Write-Host "===== 下载 DuckDB 扩展 ($ArchSuffix) ====="

    $extBase = "https://extensions.duckdb.org/v${DUCKDB_VERSION}/linux_${ArchSuffix}"
    foreach ($extName in @("odbc", "postgres")) {
        $extFile = "${extName}_scanner.duckdb_extension"
        $extUrl = "${extBase}/${extFile}"
        $extDest = Join-Path $extDir $extFile
        Download-File -Url $extUrl -Dest $extDest `
            -Desc "DuckDB ${extName}_scanner 扩展 ($ArchSuffix)"
    }

    # 3. Oracle Instant Client
    Write-Host ""
    Write-Host "===== 下载 Oracle Instant Client ($OracleArch) ====="

    $oracleBase = "https://download.oracle.com/otn_software/linux/instantclient"

    $icBasicUrl = "${oracleBase}/instantclient-basiclite-${OracleArch}.zip"
    $icBasicDest = Join-Path $oracleDir "instantclient-basiclite-${OracleArch}.zip"
    Download-File -Url $icBasicUrl -Dest $icBasicDest `
        -Desc "Oracle Instant Client Basic Lite ($OracleArch)"

    $icOdbcUrl = "${oracleBase}/instantclient-odbc-${OracleArch}.zip"
    $icOdbcDest = Join-Path $oracleDir "instantclient-odbc-${OracleArch}.zip"
    Download-File -Url $icOdbcUrl -Dest $icOdbcDest `
        -Desc "Oracle Instant Client ODBC ($OracleArch)"

    Write-Host ""
    Write-Host "[INFO]  $ArchSuffix 包准备完成:" -ForegroundColor Green
    Write-Host "  DuckDB CLI:    $([math]::Round((Get-Item $duckdbDest).Length / 1MB, 1)) MB"
    Write-Host "  ODBC 扩展:     $([math]::Round((Get-Item (Join-Path $extDir 'odbc_scanner.duckdb_extension')).Length / 1MB, 1)) MB"
    Write-Host "  PG 扩展:       $([math]::Round((Get-Item (Join-Path $extDir 'postgres_scanner.duckdb_extension')).Length / 1MB, 1)) MB"
    Write-Host "  Oracle Basic:  $([math]::Round((Get-Item $icBasicDest).Length / 1MB, 1)) MB"
    Write-Host "  Oracle ODBC:   $([math]::Round((Get-Item $icOdbcDest).Length / 1MB, 1)) MB"
}

# ========== 主流程 ==========
Write-Host "============================================================"
Write-Host "  离线部署准备 - 下载所有依赖包 (Windows)"
Write-Host "  DuckDB v${DUCKDB_VERSION}"
Write-Host "============================================================"
Write-Host ""

New-Item -ItemType Directory -Force -Path $OfflineDir | Out-Null

if ($AllArch) {
    Write-Host "[INFO]  下载全部架构 (amd64 + arm64)"
    Prepare-SingleArch -ArchSuffix "amd64" -OracleArch "linuxx64"
    Prepare-SingleArch -ArchSuffix "arm64" -OracleArch "linuxarm64"
} else {
    if ($Arch -eq "") {
        Write-Host "请选择目标 Linux 机器的 CPU 架构:"
        Write-Host "  [1] amd64 (x86_64) -- 大多数服务器"
        Write-Host "  [2] arm64 (aarch64) -- 鲲鹏/飞腾等 ARM 服务器"
        $choice = Read-Host "输入选择 (1 或 2)"
        switch ($choice) {
            "1" { $Arch = "amd64" }
            "2" { $Arch = "arm64" }
            default { Write-Host "[ERROR] 无效选择" -ForegroundColor Red; exit 1 }
        }
    }

    switch ($Arch) {
        "amd64" { Prepare-SingleArch -ArchSuffix "amd64" -OracleArch "linuxx64" }
        "arm64" { Prepare-SingleArch -ArchSuffix "arm64" -OracleArch "linuxarm64" }
    }
}

# 写入版本信息
$versionContent = "DUCKDB_VERSION=$DUCKDB_VERSION`nPREPARE_OS=Windows`nPREPARE_DATE=$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Set-Content -Path (Join-Path $OfflineDir "VERSION") -Value $versionContent -Encoding UTF8

Write-Host ""
Write-Host "============================================================" -ForegroundColor Green
Write-Host "  离线包准备完成!" -ForegroundColor Green
Write-Host "  目录: $OfflineDir" -ForegroundColor Green
Write-Host ""
Write-Host "  下一步:" -ForegroundColor Green
Write-Host "  1. 将 offline_packages 目录压缩为 zip" -ForegroundColor Green
Write-Host "  2. 拷贝到内网 Linux 目标机器 (scp/U盘/共享文件夹)" -ForegroundColor Green
Write-Host "  3. 在目标机器上解压并执行:" -ForegroundColor Green
Write-Host "     tar xzf duckdb-offline-packages.tar.gz" -ForegroundColor Green
Write-Host "     ./install_offline.sh" -ForegroundColor Green
Write-Host "============================================================" -ForegroundColor Green

在 PowerShell 中运行:

# 打开 PowerShell,进入 prepare.ps1 所在目录
cd C:\your\path	o

# 下载指定架构(交互式选择)
.\prepare.ps1

# 或直接指定架构
.\prepare.ps1 -Arch amd64
.\prepare.ps1 -Arch arm64

# 同时下载两种架构
.\prepare.ps1 -AllArch

注意: 下载的文件都是 Linux 版本的(DuckDB Linux CLI、Linux 扩展、Linux Oracle Client),Windows 只是作为"下载中转站",最终目标运行环境仍然是 Linux。下载完成后将 offline_packages 目录压缩拷贝到 Linux 目标机器即可。

下载完成后,offline_packages/ 目录结构如下:

offline_packages/
├── VERSION                                # 版本信息
└── amd64/                                 # 或 arm64/,取决于目标架构
    ├── duckdb/
    │   └── duckdb_cli-linux-amd64.zip     # DuckDB CLI
    ├── extensions/
    │   ├── odbc_scanner.duckdb_extension   # ODBC 扩展
    │   └── postgres_scanner.duckdb_extension # PostgreSQL 扩展
    └── oracle/
        ├── instantclient-basiclite-linuxx64.zip  # Oracle Basic Lite
        └── instantclient-odbc-linuxx64.zip       # Oracle ODBC

步骤 2: 打包并拷贝到内网

在 Linux 上打包

tar czf duckdb-offline-packages.tar.gz offline_packages/
scp duckdb-offline-packages.tar.gz root@192.168.x.x:/data/duckdb-oracle-gaussdb/

在 Windows 上打包

# 用 PowerShell 压缩
Compress-Archive -Path offline_packages -DestinationPath duckdb-offline-packages.tar.gz
# 注意: Linux 端解压 tar.gz 更方便,建议在 Windows 上用 7-Zip 等工具压缩为 .tar.gz
# 或直接拷贝 offline_packages 整个目录(U盘、共享文件夹等)

Windows 上压缩为 .tar.gz 可以用 7-Zip:先右键 offline_packages7-Zip → 添加到压缩包 (格式选 tar),再对生成的 .tar 文件同样操作选 gzip。也可以直接拷贝 offline_packages 文件夹到目标 Linux 机器上跳过压缩步骤。

步骤 3: 在内网目标机器上安装

在目标机器上:

cd /data/duckdb-oracle-gaussdb

# 解压离线包
tar xzf duckdb-offline-packages.tar.gz

# 将 prepare.sh、install_offline.sh、run.sh 放到项目目录
# (如果还没有的话,从代码仓库获取或手动创建)

创建 install_offline.sh 文件:

#!/usr/bin/env bash
set -euo pipefail

# ============================================================
#  离线部署 - 安装脚本
#  在内网目标机器上运行,使用 offline_packages/ 中的预下载包
#
#  前提条件:
#    1. offline_packages/ 目录已存在于项目目录下
#    2. 目标机器有 yum/apt 软件源(安装 curl, unzip, libaio, unixODBC)
#    3. root 权限
# ============================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$SCRIPT_DIR"
OFFLINE_DIR="$PROJECT_DIR/offline_packages"
RUNTIME_DIR="$PROJECT_DIR/runtime"
BIN_DIR="$PROJECT_DIR/bin"
VAR_DIR="$PROJECT_DIR/var"
EXT_DIR="$RUNTIME_DIR/extensions"
ORACLE_DIR="$PROJECT_DIR/runtime/oracle"
UNIXODBC_DIR="$PROJECT_DIR/runtime/unixodbc/lib"

# ---------- 颜色输出 ----------
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
info()  { echo -e "${GREEN}[INFO]${NC}  $*"; }
warn()  { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }

# ---------- 检测 OS 和架构 ----------
detect_env() {
    if [[ -f /etc/os-release ]]; then
        . /etc/os-release
        case "$ID" in
            ubuntu|debian|linuxmint|pop) OS_FAMILY="debian" ;;
            fedora)                        OS_FAMILY="fedora" ;;
            rhel|rocky|almalinux|centos|kylin|openeuler|anolis)   OS_FAMILY="rhel" ;;
            *) error "不支持的发行版: $ID";;
        esac
    else
        error "无法检测操作系统"
    fi

    ARCH="$(uname -m)"
    case "$ARCH" in
        x86_64)  ARCH_SUFFIX="amd64";  ;;
        aarch64) ARCH_SUFFIX="arm64";  ;;
        *) error "不支持的架构: $ARCH";;
    esac

    info "检测到: OS=$OS_FAMILY ($ID $VERSION_ID), 架构=$ARCH ($ARCH_SUFFIX)"
}

# ---------- 检查离线包 ----------
check_offline_packages() {
    if [[ ! -d "$OFFLINE_DIR/$ARCH_SUFFIX" ]]; then
        error "未找到当前架构($ARCH_SUFFIX)的离线包: $OFFLINE_DIR/$ARCH_SUFFIX"
    fi

    local pkg_dir="$OFFLINE_DIR/$ARCH_SUFFIX"
    local oracle_arch
    case "$ARCH_SUFFIX" in
        amd64) oracle_arch="linuxx64" ;;
        arm64) oracle_arch="linuxarm64" ;;
    esac

    for f in \
        "duckdb/duckdb_cli-linux-${ARCH_SUFFIX}.zip" \
        "extensions/odbc_scanner.duckdb_extension" \
        "extensions/postgres_scanner.duckdb_extension" \
        "oracle/instantclient-basiclite-${oracle_arch}.zip" \
        "oracle/instantclient-odbc-${oracle_arch}.zip"
    do
        if [[ ! -f "$pkg_dir/$f" ]]; then
            error "缺少离线包: $pkg_dir/$f"
        fi
    done

    info "离线包检查通过"
}

# ---------- 安装系统依赖 ----------
install_sys_deps() {
    info "安装系统依赖 (通过 yum/apt)..."
    if [[ "$OS_FAMILY" == "debian" ]]; then
        export DEBIAN_FRONTEND=noninteractive
        apt-get update -qq
        apt-get install -y -qq curl unzip libaio1t64 unixodbc 2>/dev/null || \
        apt-get install -y -qq curl unzip libaio1 unixodbc 2>/dev/null || \
        apt-get install -y -qq curl unzip libaio unixodbc
    elif [[ "$OS_FAMILY" == "fedora" || "$OS_FAMILY" == "rhel" ]]; then
        dnf install -y -q curl unzip libaio unixODBC 2>/dev/null || \
        yum install -y -q curl unzip libaio unixODBC
    fi
    info "系统依赖安装完成"
}

# ---------- 从离线包安装 DuckDB CLI ----------
install_duckdb() {
    info "安装 DuckDB CLI ($ARCH_SUFFIX)..."
    mkdir -p "$BIN_DIR"

    local zip_file="$OFFLINE_DIR/$ARCH_SUFFIX/duckdb/duckdb_cli-linux-${ARCH_SUFFIX}.zip"
    unzip -o "$zip_file" -d "$PROJECT_DIR/.tmp-duckdb" > /dev/null
    cp -f "$PROJECT_DIR/.tmp-duckdb/duckdb" "$BIN_DIR/duckdb"
    chmod +x "$BIN_DIR/duckdb"
    rm -rf "$PROJECT_DIR/.tmp-duckdb"

    info "DuckDB CLI: $($BIN_DIR/duckdb --version 2>/dev/null | head -1)"
}

# ---------- 从离线包复制扩展 ----------
install_extensions() {
    info "安装 DuckDB 扩展 ($ARCH_SUFFIX)..."
    mkdir -p "$EXT_DIR"

    cp -f "$OFFLINE_DIR/$ARCH_SUFFIX/extensions/odbc_scanner.duckdb_extension" \
        "$EXT_DIR/"
    cp -f "$OFFLINE_DIR/$ARCH_SUFFIX/extensions/postgres_scanner.duckdb_extension" \
        "$EXT_DIR/"

    info "扩展文件:"
    ls -lh "$EXT_DIR/"
}

# ---------- 从离线包安装 Oracle Instant Client ----------
install_oracle_client() {
    info "安装 Oracle Instant Client ($ARCH_SUFFIX)..."
    mkdir -p "$ORACLE_DIR"

    local oracle_arch
    case "$ARCH_SUFFIX" in
        amd64) oracle_arch="linuxx64" ;;
        arm64) oracle_arch="linuxarm64" ;;
    esac

    unzip -o "$OFFLINE_DIR/$ARCH_SUFFIX/oracle/instantclient-basiclite-${oracle_arch}.zip" \
        -d "$ORACLE_DIR" > /dev/null
    unzip -o "$OFFLINE_DIR/$ARCH_SUFFIX/oracle/instantclient-odbc-${oracle_arch}.zip" \
        -d "$ORACLE_DIR" > /dev/null

    ORACLE_IC_DIR="$(find "$ORACLE_DIR" -maxdepth 1 -type d -name 'instantclient_*' | head -1)"
    if [[ -z "$ORACLE_IC_DIR" ]]; then
        error "Oracle Instant Client 解压后未找到 instantclient_* 目录"
    fi

    local sqora
    sqora="$(find "$ORACLE_IC_DIR" -name 'libsqora.so.*' | head -1)"
    if [[ -n "$sqora" ]]; then
        info "找到 Oracle ODBC 驱动: $(basename "$sqora")"
    else
        error "未找到 libsqora.so (Oracle ODBC 驱动)"
    fi

    info "Oracle Instant Client: $ORACLE_IC_DIR"
}

# ---------- 提取 unixODBC 运行时库 ----------
extract_unixodbc() {
    info "提取 unixODBC 运行时库到项目目录..."
    mkdir -p "$UNIXODBC_DIR"

    local lib_search_paths=()
    if [[ "$OS_FAMILY" == "debian" ]]; then
        lib_search_paths=(/usr/lib/x86_64-linux-gnu /usr/lib/aarch64-linux-gnu /usr/lib)
    elif [[ "$OS_FAMILY" == "fedora" || "$OS_FAMILY" == "rhel" ]]; then
        lib_search_paths=(/usr/lib64 /usr/lib)
    fi

    for lib in libodbc.so.2 libodbcinst.so.2 libodbc.so.2.0.0 libodbcinst.so.2.0.0; do
        for sp in "${lib_search_paths[@]}"; do
            if [[ -f "$sp/$lib" ]]; then
                cp -a "$sp/$lib" "$UNIXODBC_DIR/" 2>/dev/null || true
                break
            fi
        done
    done

    for lib_pattern in "libaio.so.1*" "libltdl.so.7*"; do
        for sp in "${lib_search_paths[@]}"; do
            if find "$sp" -maxdepth 1 -name "$lib_pattern" -print -quit 2>/dev/null | grep -q .; then
                cp -a "$sp"/$lib_pattern "$UNIXODBC_DIR/" 2>/dev/null || true
                break
            fi
        done
    done

    info "unixODBC 运行时库:"
    ls -lh "$UNIXODBC_DIR/"
}

# ---------- 生成 ODBC 配置 ----------
generate_odbc_config() {
    info "生成 ODBC 配置..."
    mkdir -p "$VAR_DIR/odbc" "$VAR_DIR/home" "$VAR_DIR/tmp"

    local sqora_file
    sqora_file="$(find "$ORACLE_IC_DIR" -name 'libsqora.so.*' \
        __import__("subprocess").run('-name '*.so' | sort -V | tail -1)"', shell=True)
    if [[ -z "$sqora_file" ]]; then
        error "未找到 Oracle ODBC 驱动库"
    fi

    cat >"$VAR_DIR/odbc/odbcinst.ini" <<EOF
[Oracle ODBC driver]
Description=Oracle ODBC driver (self-contained)
Driver=${sqora_file}
Setup=
FileUsage=1
CPTimeout=
CPReuse=
EOF

    cat >"$VAR_DIR/odbc/odbc.ini" <<EOF
[OraclePDB1]
Driver=Oracle ODBC driver
Description=Oracle PDB1 Self-Contained
DBQ=//192.168.163.227:1527/pdb1
UserID=system

[ODBC Data Sources]
OraclePDB1=Oracle ODBC driver
EOF

    info "ODBC 配置文件已生成"
}

# ---------- 写入 env.sh ----------
write_env_file() {
    cat >"$PROJECT_DIR/env.sh" <<EOF
# 此文件由 install_offline.sh 自动生成,请勿手动编辑

export PROJECT_DIR="$PROJECT_DIR"
export DUCKDB_BIN="$PROJECT_DIR/bin/duckdb"
export ODBC_EXT="$PROJECT_DIR/runtime/extensions/odbc_scanner.duckdb_extension"
export POSTGRES_EXT="$PROJECT_DIR/runtime/extensions/postgres_scanner.duckdb_extension"
export ORACLE_IC_DIR="$ORACLE_IC_DIR"
export UNIXODBC_LIB_DIR="$UNIXODBC_DIR"
export ODBC_CONF_DIR="$PROJECT_DIR/var/odbc"
export TEMP_DIR="$PROJECT_DIR/var/tmp"
export HOME_DIR="$PROJECT_DIR/var/home"
export ORACLE_DRIVER_NAME="Oracle ODBC driver"
EOF

    info "env.sh 已生成"
}

# ---------- 验证安装 ----------
verify_installation() {
    echo ""
    info "========== 安装验证 =========="
    local errors=0

    if [[ -x "$BIN_DIR/duckdb" ]]; then
        info "[OK] DuckDB CLI: $($BIN_DIR/duckdb --version 2>/dev/null | head -1)"
    else error "[FAIL] DuckDB CLI"; errors=$((errors+1)); fi

    if [[ -f "$EXT_DIR/odbc_scanner.duckdb_extension" ]]; then
        info "[OK] ODBC 扩展"
    else error "[FAIL] ODBC 扩展"; errors=$((errors+1)); fi

    if [[ -f "$EXT_DIR/postgres_scanner.duckdb_extension" ]]; then
        info "[OK] PostgreSQL 扩展"
    else error "[FAIL] PostgreSQL 扩展"; errors=$((errors+1)); fi

    if [[ -d "$ORACLE_IC_DIR" ]]; then
        info "[OK] Oracle Instant Client"
    else error "[FAIL] Oracle Instant Client"; errors=$((errors+1)); fi

    if ls "$UNIXODBC_DIR"/libodbc.so* 1>/dev/null 2>&1; then
        info "[OK] unixODBC 库文件"
    else error "[FAIL] unixODBC 库"; errors=$((errors+1)); fi

    if [[ $errors -gt 0 ]]; then error "安装失败: $errors 项错误"; fi

    info "========== 离线安装完成 =========="
    echo ""
    info "下一步: cd $PROJECT_DIR && ./run.sh"
}

# ========== 主流程 ==========
main() {
    echo "============================================================"
    echo "  DuckDB + Oracle + GaussDB 离线安装"
    echo "  OS: $(. /etc/os-release 2>/dev/null && echo "$ID $VERSION_ID")  架构: $(uname -m)"
    echo "============================================================"
    echo ""

    mkdir -p "$RUNTIME_DIR" "$VAR_DIR"

    detect_env
    check_offline_packages
    install_sys_deps
    install_duckdb
    install_extensions
    install_oracle_client
    extract_unixodbc
    generate_odbc_config
    write_env_file
    verify_installation
}

main "$@"

赋予执行权限并运行:

chmod +x install_offline.sh
./install_offline.sh

离线安装过程与在线安装基本一致,唯一区别是不执行任何网络下载——DuckDB CLI、扩展、Oracle 客户端全部从 offline_packages/ 目录读取。只有四个基础系统包(curl、unzip、libaio、unixODBC)通过 yum/apt 从本地软件源安装。

安装完成后,运行脚本 run.sh 与在线部署完全相同,无需任何修改。

离线部署 vs 在线部署对比

对比项在线部署 (install.sh)离线部署 (install_offline.sh)
网络要求需要外网访问仅需内网 yum/apt 源
DuckDB CLI从 GitHub 下载offline_packages/ 解压
DuckDB 扩展从 CDN/DuckDB INSTALL 下载offline_packages/ 复制
Oracle 客户端从 Oracle 官网下载offline_packages/ 解压
系统依赖同(curl, unzip, libaio, unixODBC)
unixODBC 库从系统提取从系统提取
运行时完全相同完全相同
准备工作需要先在有网机器运行 prepare.sh

关于 run.sh 的说明

离线部署和在线部署共用同一个 run.shrun.sh 只使用项目目录内的文件(DuckDB CLI、扩展、配置),不依赖网络。因此无论用哪种方式安装,run.sh 的行为完全一致。

七、方案优势与局限

优势

  1. 完全自包含: 所有依赖打包在项目目录内,不污染系统环境
  2. 多平台支持: 同时兼容 Debian/Ubuntu 和 Fedora/RHEL,amd64 和 arm64
  3. 零配置运行: 安装脚本自动处理所有配置,运行脚本自动设置环境变量
  4. 灵活扩展: 支持通过环境变量覆盖连接参数,SQL 模板可自定义
  5. 无 root 运行时需求: 安装后,运行 run.sh 不需要 root 权限

局限

  1. 网络依赖: 安装时需要网络下载 DuckDB、扩展和 Oracle Client
  2. 架构绑定: 打包后的目录只能在相同 CPU 架构上运行(amd64 不能在 arm64 上用,反之亦然)
  3. glibc 兼容性: 目标机器的 glibc 版本不能比构建环境明显更老
  4. Oracle ODBC 性能: ODBC 连接有额外的序列化/反序列化开销,不适合大批量数据传输
  5. DuckDB 版本绑定: 扩展版本必须与 DuckDB CLI 版本严格匹配

八、多平台验证

为了验证方案的通用性,在两台不同操作系统的服务器上进行了测试。

Ubuntu 22.04 x86_64 — 首次验证

============================================================
  DuckDB + Oracle + GaussDB 自包含安装
  OS: ubuntu 22.04  架构: x86_64
============================================================

[INFO]  检测到: OS=debian (ubuntu 22.04), 架构=x86_64 (amd64)
...
[INFO]  ========== 安装完成 ==========

运行结果:

┌───────────────┬─────────────────────┬────────────────┬───────────────────────────────┐
│ oracle_result │   oracle_sysdate    │ gaussdb_result │       gaussdb_timestamp       │
│    varchar    │       varchar       │    varchar     │   timestamp with time zone    │
├───────────────┼─────────────────────┼────────────────┼───────────────────────────────┤
│ Oracle_OK     │ 2026-05-22 07:09:31 │ GaussDB_OK     │ 2026-05-22 07:09:34.067699+00 │
└───────────────┴─────────────────────┴────────────────┴───────────────────────────────┘
┌───────────┬────────────────────────────────────────────────────────────────────────┐
│ db_source │                                version                                 │
│  varchar  │                                varchar                                 │
├───────────┼────────────────────────────────────────────────────────────────────────┤
│ Oracle    │ Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production │
│ GaussDB   │ gaussdb (GaussDB Kernel 506.0.0.SPC0500 build d474632c) compiled at... │
└───────────┴────────────────────────────────────────────────────────────────────────┘

Kylin V10 x86_64 — 国产系统验证

Kylin V10 是基于 RHEL 的国产操作系统,其 /etc/os-releaseID=kylin,不在标准 RHEL 识别列表中。因此需要在 install.shdetect_env() 函数中将 kylin 加入 rhel 分支:

# 修改前
rhel|rocky|almalinux|centos)   OS_FAMILY="rhel" ;;

# 修改后
rhel|rocky|almalinux|centos|kylin|openeuler|anolis)   OS_FAMILY="rhel" ;;

安装过程输出:

============================================================
  DuckDB + Oracle + GaussDB 自包含安装
  OS: kylin V10  架构: x86_64
============================================================

[INFO]  检测到: OS=rhel (kylin V10), 架构=x86_64 (amd64)
[INFO]  安装系统依赖...
[INFO]  系统依赖安装完成
[INFO]  安装 DuckDB CLI v1.5.3 (amd64)...
[WARN]  直连失败, 尝试通过代理下载...
[INFO]  DuckDB CLI: v1.5.3 (Variegata) 14eca11bd9
[INFO]  下载 DuckDB 扩展 (amd64)...
[INFO]  通过 DuckDB INSTALL 下载: odbc
[INFO]  下载成功: odbc_scanner
[INFO]  通过 DuckDB INSTALL 下载: postgres
[INFO]  下载成功: postgres_scanner
[INFO]  安装 Oracle Instant Client (linuxx64)...
[INFO]  找到 Oracle ODBC 驱动: libsqora.so.23.1
[INFO]  提取 unixODBC 运行时库到项目目录...
[INFO]  ========== 安装完成 ==========

注意: GitHub 直连不通时,脚本自动通过 ghfast.top 代理下载了 DuckDB CLI;扩展则通过 DuckDB 内置的 INSTALL 命令成功下载。

运行结果:

============================================================
  DuckDB 跨库查询: Oracle + GaussDB
  Oracle:  system@192.168.163.227:1527/pdb1
  GaussDB: admin@192.168.163.227:15432/postgres
============================================================

┌───────────┬─────────────┐
│  STATUS   │ ORACLE_TIME │
│  varchar  │    date     │
├───────────┼─────────────┤
│ Oracle_OK │ 2026-05-22  │
└───────────┴─────────────┘
┌────────────┬───────────────────────────────┐
│   status   │         gaussdb_time          │
│  varchar   │   timestamp with time zone    │
├────────────┼───────────────────────────────┤
│ GaussDB_OK │ 2026-05-22 16:26:36.448552+08 │
└────────────┴───────────────────────────────┘
┌───────────────┬─────────────────────┬────────────────┬───────────────────────────────┐
│ oracle_result │   oracle_sysdate    │ gaussdb_result │       gaussdb_timestamp       │
│    varchar    │       varchar       │    varchar     │   timestamp with time zone    │
├───────────────┼─────────────────────┼────────────────┼───────────────────────────────┤
│ Oracle_OK     │ 2026-05-22 08:03:14 │ GaussDB_OK     │ 2026-05-22 16:03:17.636536+08 │
└───────────────┴─────────────────────┴────────────────┴───────────────────────────────┘
┌───────────┬────────────────────────────────────────────────────────────────────────┐
│ db_source │                                version                                 │
│  varchar  │                                varchar                                 │
├───────────┼────────────────────────────────────────────────────────────────────────┤
│ Oracle    │ Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production │
│ GaussDB   │ gaussdb (GaussDB Kernel 506.0.0.SPC0500 build d474632c) compiled at... │
└───────────┴────────────────────────────────────────────────────────────────────────┘

验证结论

验证项Ubuntu 22.04Kylin V10
OS 识别debianrhel (自动)
系统依赖安装apt-get已预装
DuckDB CLI 下载直连代理 (自动回退)
扩展下载DuckDB INSTALLDuckDB INSTALL
Oracle 连接通过通过
GaussDB 连接通过通过
跨库联合查询通过通过
版本查询Oracle 19c + GaussDB 506Oracle 19c + GaussDB 506

两台服务器均一次安装成功,无需任何手动干预,证明该方案的跨平台兼容性良好。

总结

本文实现了一个完全自包含的 DuckDB 跨库查询方案,核心要点:

  • DuckDB 通过 ODBC scanner 扩展连接 Oracle
  • DuckDB 通过 PostgreSQL scanner 扩展连接 GaussDB
  • 连接 GaussDB 必须设置 SET pg_use_text_protocol=true;
  • 所有运行时依赖打包在项目目录内,通过环境变量隔离
  • 安装脚本自动检测 OS 发行版和 CPU 架构,适配多种 Linux 环境

整个方案只需两个脚本(install.sh + run.sh),即可在任意 Linux 服务器上快速部署并实现 Oracle + GaussDB 的跨库联合查询。

参考链接

本文完全由wps灵犀claw生成,最终article.md文件我一字没改
image-WzQD.png
image-sRAo.png

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
博主关闭了所有页面的评论