⌘+k ctrl+k
1.4 (LTS)
搜索快捷键 cmd + k | ctrl + k
ODBC 101:DuckDB 的鸭子主题 ODBC 指南

什么是 ODBC?

ODBC 代表开放数据库互连(Open Database Connectivity),是一项允许不同程序与不同数据库(当然也包括 DuckDB)进行通信的标准。这使得构建能够适配多种数据库的程序变得更容易,从而节省了时间,因为开发人员无需为连接每个数据库编写自定义代码。相反,他们可以使用标准化的 ODBC 接口,这降低了开发时间和成本,并且使程序更易于维护。然而,ODBC 比使用原生驱动程序等其他数据库连接方法要慢,因为它在应用程序和数据库之间增加了一个额外的抽象层。此外,由于 DuckDB 是基于列的,而 ODBC 是基于行的,因此在将 ODBC 与 DuckDB 一起使用时可能会存在一些低效问题。

本页面中包含指向官方 Microsoft ODBC 文档的链接,这是了解 ODBC 的绝佳资源。

基本概念

句柄 (Handles)

句柄是指向特定 ODBC 对象的指针,用于与数据库进行交互。有几种不同类型的句柄,每种都有不同的用途,包括环境句柄、连接句柄、语句句柄和描述符句柄。句柄使用 SQLAllocHandle 进行分配,该函数接收要分配的句柄类型和一个指向句柄的指针作为输入,然后驱动程序创建一个指定类型的新句柄并将其返回给应用程序。

DuckDB ODBC 驱动程序具有以下句柄类型。

环境

句柄名称 环境
类型名称 SQL_HANDLE_ENV
描述 管理 ODBC 操作的环境设置,并提供访问数据的全局上下文。
使用场景 初始化 ODBC、管理驱动程序行为、资源分配。
附加信息 必须在应用程序启动时分配一次,并在结束时释放。

连接

句柄名称 连接
类型名称 SQL_HANDLE_DBC
描述 代表与数据源的连接。用于建立、管理和终止连接。定义了驱动程序以及在驱动程序内使用的数据源。
使用场景 建立数据库连接、管理连接状态。
附加信息 可以根据需要创建多个连接句柄,从而允许同时连接到多个数据源。注意:分配连接句柄并不会建立连接,必须先进行分配,然后在建立连接后使用。

语句

句柄名称 语句
类型名称 SQL_HANDLE_STMT
描述 处理 SQL 语句的执行以及返回的结果集。
使用场景 执行 SQL 查询、获取结果集、管理语句选项。
附加信息 为了方便并发查询的执行,每个连接可以分配多个句柄。

描述符

句柄名称 描述符
类型名称 SQL_HANDLE_DESC
描述 描述数据结构或参数的属性,并允许应用程序指定要绑定/检索的数据结构。
使用场景 描述表结构、结果集、将列绑定到应用程序缓冲区。
附加信息 用于需要显式定义数据结构的情况,例如在参数绑定或获取结果集期间。它们在分配语句时会自动分配,但也可以显式分配。

连接

第一步是连接到数据源,以便应用程序可以执行数据库操作。首先,应用程序必须分配一个环境句柄,然后分配一个连接句柄。连接句柄随后用于连接到数据源。有两个函数可用于连接到数据源,即 SQLDriverConnectSQLConnect。前者用于使用连接字符串连接到数据源,而后者用于使用 DSN 连接到数据源。

连接字符串

连接字符串是包含连接到数据源所需信息的字符串。它被格式化为以分号分隔的键值对列表,但 DuckDB 目前仅使用 DSN,而忽略其余参数。

DSN

DSN(数据源名称)是标识数据库的字符串。它可以是文件路径、URL 或数据库名称。例如,C:\Users\me\duckdb.dbDuckDB 都是有效的 DSN。有关 DSN 的更多信息,可以在 SQL Server 文档的“选择数据源或驱动程序”页面中找到。

错误处理与诊断

ODBC 中的所有函数都会返回一个代表函数成功或失败的代码。这使得错误处理变得简单,因为应用程序只需检查每个函数调用的返回代码即可确定其是否成功。如果不成功,应用程序可以使用 SQLGetDiagRec 函数来检索错误信息。下表定义了返回代码

返回代码 描述
SQL_SUCCESS 函数执行成功。
SQL_SUCCESS_WITH_INFO 函数执行成功,但有额外信息可用,包括警告。
SQL_ERROR 函数执行失败。
SQL_INVALID_HANDLE 提供的句柄无效,表示编程错误,例如句柄在使用前未分配或类型错误。
SQL_NO_DATA 函数执行成功,但没有更多数据可用。
SQL_NEED_DATA 需要更多数据,例如在执行时发送参数数据,或需要额外的连接信息。
SQL_STILL_EXECUTING 异步执行的函数仍在执行中。

缓冲区与绑定

缓冲区是用于存储数据的一块内存。缓冲区用于存储从数据库检索的数据,或向数据库发送数据。缓冲区由应用程序分配,然后使用 SQLBindColSQLBindParameter 函数绑定到结果集中的列或查询中的参数。当应用程序从结果集中获取行或执行查询时,数据会存储在缓冲区中。当应用程序向数据库发送查询时,缓冲区中的数据会发送到数据库。

设置应用程序

以下是使用 ODBC 连接数据库、执行查询并在 C++ 中获取结果的应用程序设置分步指南。

要安装驱动程序及所需其他组件,请遵循这些说明

1. 包含 SQL 头文件

第一步是包含 SQL 头文件

#include <sql.h>
#include <sqlext.h>

这些文件包含 ODBC 函数的定义以及 ODBC 使用的数据类型。要使用这些头文件,必须安装 unixodbc

在 macOS 上

brew install unixodbc

在 Ubuntu 和 Debian 上

sudo apt-get install -y unixodbc-dev

在 Fedora、CentOS 和 Red Hat 上

sudo yum install -y unixODBC-devel

记得在你的 CFLAGS 中包含头文件位置。

对于 MAKEFILE

CFLAGS=-I/usr/local/include
# or
CFLAGS=-/opt/homebrew/Cellar/unixodbc/2.3.11/include

对于 CMAKE

include_directories(/usr/local/include)
# or
include_directories(/opt/homebrew/Cellar/unixodbc/2.3.11/include)

你还需要在 CMAKEMAKEFILE 中链接库。对于 CMAKE

target_link_libraries(ODBC_application /path/to/duckdb_odbc/libduckdb_odbc.dylib)

对于 MAKEFILE

LDLIBS=-L/path/to/duckdb_odbc/libduckdb_odbc.dylib

2. 定义 ODBC 句柄并连接到数据库

2.a. 使用 SQLConnect 连接

然后设置 ODBC 句柄,分配它们,并连接到数据库。首先分配环境句柄,然后将环境设置为 ODBC 版本 3,接着分配连接句柄,最后建立与数据库的连接。以下代码片段展示了如何操作

SQLHANDLE env;
SQLHANDLE dbc;

SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);

SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);

SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);

std::string dsn = "DSN=duckdbmemory";
SQLConnect(dbc, (SQLCHAR*)dsn.c_str(), SQL_NTS, NULL, 0, NULL, 0);

std::cout << "Connected!" << std::endl;

2.b. 使用 SQLDriverConnect 连接

或者,你可以使用 SQLDriverConnect 连接到 ODBC 驱动程序。SQLDriverConnect 接受一个连接字符串,你可以在其中使用任何可用的 DuckDB 配置选项来配置数据库。

SQLHANDLE env;
SQLHANDLE dbc;

SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);

SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);

SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);

SQLCHAR str[1024];
SQLSMALLINT strl;
std::string dsn = "DSN=DuckDB;access_mode=READ_ONLY"
SQLDriverConnect(dbc, nullptr, (SQLCHAR*)dsn.c_str(), SQL_NTS, str, sizeof(str), &strl, SQL_DRIVER_COMPLETE)

std::cout << "Connected!" << std::endl;

3. 添加查询

现在应用程序已设置完毕,我们可以向其中添加查询。首先,我们需要分配一个语句句柄

SQLHANDLE stmt;
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);

然后我们可以执行查询

SQLExecDirect(stmt, (SQLCHAR*)"SELECT * FROM integers", SQL_NTS);

4. 获取结果

既然已经执行了查询,我们就可以获取结果。首先,我们需要将结果集中的列绑定到缓冲区

SQLLEN int_val;
SQLLEN null_val;
SQLBindCol(stmt, 1, SQL_C_SLONG, &int_val, 0, &null_val);

然后我们可以获取结果

SQLFetch(stmt);

5. 处理结果

现在我们有了结果,可以对它们进行任何处理。例如,我们可以将它们打印出来

std::cout << "Value: " << int_val << std::endl;

你还可以执行其他查询并执行其他数据库操作,如插入、更新或删除数据。

6. 释放句柄并断开连接

最后,我们需要释放句柄并与数据库断开连接。首先,我们需要释放语句句柄

SQLFreeHandle(SQL_HANDLE_STMT, stmt);

然后我们需要与数据库断开连接

SQLDisconnect(dbc);

最后,我们需要释放连接句柄和环境句柄

SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);

释放连接句柄和环境句柄只能在与数据库的连接关闭后完成。如果在断开连接之前尝试释放它们,将会导致错误。

示例应用程序

以下是一个示例应用程序,其中包含一个 cpp 文件,该文件连接到数据库、执行查询、获取结果并打印它们。它还断开与数据库的连接并释放句柄,并包含一个检查 ODBC 函数返回值的函数。它还包含一个可用于构建应用程序的 CMakeLists.txt 文件。

示例 .cpp 文件

#include <iostream>
#include <sql.h>
#include <sqlext.h>

void check_ret(SQLRETURN ret, std::string msg) {
    if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
        std::cout << ret << ": " << msg << " failed" << std::endl;
        exit(1);
    }
    if (ret == SQL_SUCCESS_WITH_INFO) {
        std::cout << ret << ": " << msg << " succeeded with info" << std::endl;
    }
}

int main() {
    SQLHANDLE env;
    SQLHANDLE dbc;
    SQLRETURN ret;

    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
    check_ret(ret, "SQLAllocHandle(env)");

    ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
    check_ret(ret, "SQLSetEnvAttr");

    ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
    check_ret(ret, "SQLAllocHandle(dbc)");

    std::string dsn = "DSN=duckdbmemory";
    ret = SQLConnect(dbc, (SQLCHAR*)dsn.c_str(), SQL_NTS, NULL, 0, NULL, 0);
    check_ret(ret, "SQLConnect");

    std::cout << "Connected!" << std::endl;

    SQLHANDLE stmt;
    ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
    check_ret(ret, "SQLAllocHandle(stmt)");

    ret = SQLExecDirect(stmt, (SQLCHAR*)"SELECT * FROM integers", SQL_NTS);
    check_ret(ret, "SQLExecDirect(SELECT * FROM integers)");

    SQLLEN int_val;
    SQLLEN null_val;
    ret = SQLBindCol(stmt, 1, SQL_C_SLONG, &int_val, 0, &null_val);
    check_ret(ret, "SQLBindCol");

    ret = SQLFetch(stmt);
    check_ret(ret, "SQLFetch");

    std::cout << "Value: " << int_val << std::endl;

    ret = SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    check_ret(ret, "SQLFreeHandle(stmt)");

    ret = SQLDisconnect(dbc);
    check_ret(ret, "SQLDisconnect");

    ret = SQLFreeHandle(SQL_HANDLE_DBC, dbc);
    check_ret(ret, "SQLFreeHandle(dbc)");

    ret = SQLFreeHandle(SQL_HANDLE_ENV, env);
    check_ret(ret, "SQLFreeHandle(env)");
}

示例 CMakelists.txt 文件

cmake_minimum_required(VERSION 3.25)
project(ODBC_Tester_App)

set(CMAKE_CXX_STANDARD 17)
include_directories(/opt/homebrew/Cellar/unixodbc/2.3.11/include)

add_executable(ODBC_Tester_App main.cpp)
target_link_libraries(ODBC_Tester_App /duckdb_odbc/libduckdb_odbc.dylib)
© 2025 DuckDB 基金会,阿姆斯特丹,荷兰
行为准则 商标使用指南