ADBC 驱动管理器与清单

注意

本文档介绍了如何使用 驱动管理器 加载驱动程序。通常情况下,使用 ADBC 并不强制要求使用驱动管理器,但它允许加载与应用程序使用不同语言编写的驱动程序,并改善在单个应用程序中使用多个驱动程序时的体验。有关驱动管理器工作原理的更多信息,请参阅 驱动程序与驱动管理器如何协同工作

通过驱动管理器加载驱动程序有两种方式:

  1. 直接指定要加载的动态库。

  2. 引用一个 驱动程序清单 文件,其中包含元数据以及要加载的动态库的位置。

无论使用哪种方法,您都可以将动态库或驱动程序清单指定为驱动管理器的 driver 选项;如果您的驱动管理器库提供了显式函数(例如 C++,见下文示例),也可以使用该函数来加载驱动程序。

注意

除了 driver 选项外,还有一个 入口点 (entrypoint) 选项,如果驱动程序使用了非默认的入口点,则应使用此选项。

直接加载驱动程序

通过驱动管理器加载驱动程序的最简单机制是提供动态库的文件路径作为驱动程序名称。

您可以将驱动程序指定为驱动管理器的 driver 选项,或者使用 AdbcLoadDriver() 直接加载驱动程序。

struct AdbcDatabase database;
struct AdbcError error;

std::memset(&database, 0, sizeof(database));
std::memset(&error, 0, sizeof(error));

auto status = AdbcDatabaseNew(&database, &error);
// if status != ADBC_STATUS_OK then handle the error

// Load the driver by setting the "driver" option
status = AdbcDatabaseSetOption(&database, "driver", "/path/to/libadbc_driver.so", &error);
// if status != ADBC_STATUS_OK then handle the error

// Alternatively, load directly with AdbcLoadDriver
struct AdbcDriver driver;
struct AdbcError error;

std::memset(&driver, 0, sizeof(driver));
std::memset(&error, 0, sizeof(error));

auto status = AdbcLoadDriver("/path/to/libadbc_driver.so", nullptr,
  ADBC_VERSION_1_1_0, &driver, &error);
// if status != ADBC_STATUS_OK then handle the error

您可以通过 GADBCDatabase 将其用作驱动程序。

GError *error = NULL;
GADBCDatabase *database = gadbc_database_new(&error);
if (!database) {
  /* handle error */
}
if (!gadbc_database_set_option(database, "driver", "/path/to/libadbc_driver.so", &error)) {
  /* handle error */
}

在 Go 中加载驱动程序的方法类似。

import (
  "context"

  "github.com/apache/arrow-adbc/go/adbc"
  "github.com/apache/arrow-adbc/go/adbc/drivermgr"
)

func main() {
  var drv drivermgr.Driver
  db, err := drv.NewDatabase(map[string]string{
    "driver": "/path/to/libadbc_driver.so",
  })
  if err != nil {
    // handle error
  }
  defer db.Close()

  // ... do stuff
}

您可以按如下方式使用 DBAPI 接口:

import adbc_driver_manager

with adbc_driver_manager.dbapi.connect(driver="/path/to/libadbc_driver.so") as conn:
    # use the connection
    pass

您可以按如下方式使用 DBAPI 接口:

library(adbcdrivermanager)
con <- adbc_driver("/path/to/libadbc_driver.so") |>
  adbc_database_init(uri = "...") |>
  adbc_connection_init()

您可以按如下方式使用 ADBC::Database

require "adbc"

ADBC::Database.open(driver: "/path/to/libadbc_driver.so") do |database|
  # use the database
end

Rust 拥有一种 ManagedDriver 类型,其中包含用于加载驱动程序的静态方法。

use adbc_core::options::AdbcVersion;
use adbc_core::driver_manager::ManagedDriver;

fn get_driver() -> ManagedDriver {
    ManagedDriver::load_from_name("/path/to/libadbc_driver.so", None, AdbcVersion::V100).unwrap()
}

作为传递动态库完整路径的替代方案,您可能更喜欢使用 LD_LIBRARY_PATH(或类似的环境变量,具体取决于您的操作系统)并仅指定文件名(例如:使用 libadbc_driver.so 而不是 /path/to/libadbc_driver.so)。

然而,必须知道动态库的路径或将其添加到 LD_LIBRARY_PATH 中可能会给安全性、可复现性和易用性带来困难。出于这个原因,引入了驱动程序清单的概念。

驱动程序清单

一个 驱动程序清单 是一个 TOML 文件,其中包含有关驱动程序的元数据以及要加载的共享库的位置。如果驱动管理器直接获得了共享库路径,它就可以定位到该清单并使用它来加载驱动程序。这使得驱动程序的安装和配置共享更具可移植性。此外,还可以开发相关工具来自动管理驱动程序的安装。

清单结构

尽管大多数键是可选的,我们还是定义了一组预期出现在驱动程序清单中的键和结构。这确保了驱动管理器实现以及为管理驱动程序安装而编写的工具能够一致地处理清单。

以下是驱动程序清单的一个示例:

manifest_version = 1

name = 'Driver Display Name'
version = '1.0.0' # driver version
publisher = 'string to identify the publisher'
license = 'Apache-2.0' # or otherwise
url = 'https://example.com' # URL with more info about the driver
                            # such as a github link or documentation.

[ADBC]
version = '1.1.0' # Maximum supported ADBC spec version

[ADBC.features]
supported = [] # list of strings such as 'bulk insert'
unsupported = [] # list of strings such as 'async'

[Driver]
entrypoint = 'AdbcDriverInit' # entrypoint to use if not using default
# You can provide just a single path
# shared = '/path/to/libadbc_driver.so'

# or you can provide platform-specific paths for scenarios where the driver
# is distributed with multiple platforms supported by a single package.
[Driver.shared]
# paths to shared libraries to load based on platform tuple
linux_amd64 = '/path/to/libadbc_driver.so'
osx_amd64 = '/path/to/libadbc_driver.dylib'
windows_amd64 = 'C:\\path\\to\\adbc_driver.dll'
# ... other platforms as needed

通常,唯一 必须 的键是 Driver.shared,该键必须存在,且必须是字符串(单一路径)或特定于平台的路径表。Driver.shared 是成功加载驱动程序清单所需的唯一键。其他键是可选的,但它们提供了关于驱动程序的有用元数据。

manifest_version 键如果存在,必须设置为 1。它默认为 1,目前只能设置为 1。驱动管理器在读取 manifest_version 大于 1 的清单时必须报错。

平台元组 (Platform Tuples)

由于清单使用平台元组来指定驱动程序可能构建的目标系统,因此创建一种一致的元组命名方式非常重要。具体来说,需要为所有读取和/或写入清单文件的系统提供一致的操作系统 (OS) 和架构组合命名。

因此,下表应被视为权威列表:

操作系统

元组名称

Linux

linux

macOS

macos

Windows

windows

FreeBSD

freebsd

OpenBSD

openbsd

架构

元组名称

i386

x86

x86

x86

x86-64

amd64

x64

amd64

amd64

amd64

arm (32-bit)

arm

armbe (32-bit)

armbe

arm64be

arm64be

aarch64

arm64

arm64

arm64

s390x

s390x

ppc

powerpc

ppc64

powerpc64

ppc64le

powerpc64le

riscv

riscv

riscv64

riscv64

sparc

sparc

sparc64

sparc64

Wasm (32-bit)

wasm32

Wasm (64-bit)

wasm64

平台元组的构建方式为:<OS>_<Architecture>,例如:linux_amd64

注意

对于替代场景(如使用 musl 而非 GNU C Library (glibc) 或 MinGW),元组应具有该环境的适当后缀。例如 linux_amd64_muslwindows_amd64_mingw

清单位置与发现

当为驱动管理器提供要加载的驱动程序名称时,对于如何定位该驱动程序有明确定义的行为。这种定义的行为允许在不同的驱动管理器实现和绑定之间保持一致,同时为驱动程序的安装方式提供了灵活性。

给定一个驱动程序名称,首先需要将其解析为要加载的动态库,或者包含该动态库路径的驱动程序清单。以下流程图描述了如何进行此解析:

Flowchart diagram showing the how the driver manager resolves a simple driver name and eventually attempts to load the driver or returns an error.

流程图显示了驱动管理器如何解析简单的驱动程序名称,并最终尝试加载驱动程序或返回错误。

因此,如果驱动程序名称是文件路径,驱动管理器将尝试直接加载该文件。如果没有提供扩展名,它会首先查找带有 .toml 扩展名的文件;如果失败,它将查找适用于当前平台的扩展名(例如,Linux 上为 .so,macOS 上为 .dylib,Windows 上为 .dll)。

注意

如果驱动程序名称是相对路径,它将相对于当前工作目录进行解析。因此,出于安全考虑,这需要通过一个选项来显式启用,否则将报错。

如流程图所示,如果驱动程序名称是一个没有扩展名且不是文件路径的字符串,驱动管理器将首先搜索相应的清单文件,然后再退回到查看 LD_LIBRARY_PATH(或您操作系统的等效项)是否能找到具有给定名称的库。搜索清单文件是通过查找带有 .toml 扩展名的同名文件来完成的(例如,如果您传递 sqlite 作为驱动程序名称,它会寻找 sqlite.toml)。系统提供了一些选项来控制搜索清单的目录,其行为因平台而略有不同。

AdbcLoadFlags 类型是一组用于控制搜索目录的位标志。标志包括:

这些可以提供给 AdbcFindLoadDriver(),或者通过使用 AdbcDriverManagerDatabaseSetLoadFlags() 来设置。

GADBCLoadFlags 类型是一组用于控制搜索目录的位标志。标志包括:

  • GADBC_LOAD_SEARCH_ENV - 搜索环境变量 ADBC_DRIVER_PATH 中的目录路径(当通过 conda 构建或安装时,搜索 conda 环境)

  • GADBC_LOAD_FLAG_SEARCH_USER - 搜索用户配置目录

  • GADBC_LOAD_FLAG_SEARCH_SYSTEM - 搜索系统配置目录

  • GADBC_LOAD_FLAG_ALLOW_RELATIVE_PATHS - 允许提供相对路径

  • GADBC_LOAD_FLAG_DEFAULT - 设置所有标志的默认值

这些可以通过使用 gadbc_database_set_load_flags() 来提供。

drivermgr 包默认使用加载标志的默认值,即搜索环境变量、用户配置目录和系统配置目录。在调用 NewDatabaseNewDatabaseWithContext 时,您可以传递 drivermgr.LoadFlagsOptionKey 选项,其值为使用 strconv.Itoa 转换的标志值。这些标志在 drivermgr 包中定义为常量:

  • drivermgr.LoadFlagsSearchEnv - 搜索环境变量 ADBC_DRIVER_PATH 中的目录路径

  • drivermgr.LoadFlagsSearchUser - 搜索用户配置目录

  • drivermgr.LoadFlagsSearchSystem - 搜索系统配置目录

  • drivermgr.LoadFlagsAllowRelativePaths - 允许使用相对路径

  • drivermgr.LoadFlagsDefault - 设置所有标志的默认值

load_flags 作为选项传递给 AdbcDatabase(或通过 adbc_driver_manager.dbapi.connect 中的 db_kwargs),允许您通过使用该选项的值作为所需加载标志的位掩码来控制要搜索的目录。

使用 adbc_driver(..., load_flags = adbc_load_flags()) 来传递有关如何定位清单指定的驱动程序的选项给驱动管理器。

ADBC::LoadFlags 类是一组用于控制搜索目录的位标志。标志包括:

  • ADBC::LoadFlags::SEARCH_ENV - 搜索环境变量 ADBC_DRIVER_PATH 中的目录路径(当通过 conda 构建或安装时,搜索 conda 环境)

  • ADBC::LoadFlags::SEARCH_USER - 搜索用户配置目录

  • ADBC::LoadFlags::SEARCH_SYSTEM - 搜索系统配置目录

  • ADBC::LoadFlags::ALLOW_RELATIVE_PATHS - 允许提供相对路径

  • ADBC::LoadFlags::DEFAULT - 设置所有标志的默认值

这些可以通过使用 ADBC::Database#load_flags= 提供。将 load_flags 作为选项传递给 AdbcDatabase(或通过 adbc_driver_manager.dbapi.connect 中的 db_kwargs),允许您通过使用该选项的值作为所需加载标志的位掩码来控制要搜索的目录。

ManagedDriver 类型具有一个 load_from_name 方法,该方法接受一个可选的 load_flags 参数。标志作为 u32 类型,使用 adbc_core::driver_manager::LoadFlags 类型,该类型包含以下常量:

  • LOAD_FLAG_SEARCH_ENV - 搜索环境变量 ADBC_DRIVER_PATH 中的目录路径(当通过 conda 构建或安装时,搜索 conda 环境)

  • LOAD_FLAG_SEARCH_USER - 搜索用户配置目录

  • LOAD_FLAG_SEARCH_SYSTEM - 搜索系统配置目录

  • LOAD_FLAG_ALLOW_RELATIVE_PATHS - 允许使用相对路径

  • LOAD_FLAG_DEFAULT - 设置所有标志的默认值

警告

驱动程序清单文件不能命名为 profile.tomlprofile 名称保留用于 连接配置文件。将其用作驱动程序清单的基名会与驱动管理器解析以 profile:// 开头的 URI 的方式产生冲突。

类 Unix 平台

对于类 Unix 平台(例如 Linux、macOS),驱动管理器会根据提供的选项按以下顺序搜索目录:

  1. 如果设置了 LOAD_FLAG_SEARCH_ENV 加载选项,则会搜索环境变量 ADBC_DRIVER_PATH 中的路径。

    • ADBC_DRIVER_PATH 是一个冒号分隔的目录列表。

  2. 如果指定了其他搜索路径,也会搜索这些路径。

    • Python 驱动管理器在 venv 虚拟环境中运行时,会自动将 $VIRTUAL_ENV/etc/adbc/drivers 添加到搜索路径中。

  3. 如果驱动管理器通过 conda 构建或安装,并且设置了 LOAD_FLAG_SEARCH_ENV 加载选项,则会搜索 $CONDA_PREFIX/etc/adbc/drivers

  4. 如果设置了 LOAD_FLAG_SEARCH_USER 加载选项,则会搜索用户级配置目录。

    • 在 macOS 上,这将是 ~/Library/Application Support/ADBC/Drivers

    • 在 Linux(以及其他类 Unix 平台)上,首先检查 XDG_CONFIG_HOME 环境变量。如果设置了该变量,驱动管理器将搜索 $XDG_CONFIG_HOME/adbc/drivers;否则,它将搜索 ~/.config/adbc/drivers

  5. 如果设置了 LOAD_FLAG_SEARCH_SYSTEM 加载选项,则会搜索系统级配置目录。

    • 在 macOS 上,如果存在,这将是 /Library/Application Support/ADBC/Drivers

    • 在 Linux(以及其他类 Unix 平台)上,如果存在,这将是 /etc/adbc/drivers

Windows

Windows 上的情况略有不同,驱动管理器也会像对待 ODBC 驱动程序一样在注册表中搜索驱动程序信息。在 Windows 上搜索清单的行为如下:

  1. 如果设置了 LOAD_FLAG_SEARCH_ENV 加载选项,则会搜索环境变量 ADBC_DRIVER_PATH 中的路径。

    • ADBC_DRIVER_PATH 是一个分号分隔的目录列表。

  2. 如果指定了其他搜索路径,也会搜索这些路径。

    • Python 驱动管理器在 venv 虚拟环境中运行时,会自动将 $VIRTUAL_ENV\etc\adbc\drivers 添加到搜索路径中。

  3. 如果驱动管理器通过 conda 构建或安装,并且设置了 LOAD_FLAG_SEARCH_ENV 加载选项,则会搜索 $CONDA_PREFIX\etc\adbc\drivers

  4. 如果设置了 LOAD_FLAG_SEARCH_USER 加载选项,则会搜索用户级配置。

    • 首先,在注册表中搜索键 HKEY_CURRENT_USER\SOFTWARE\ADBC\Drivers\${name}。如果存在,则使用以下子键:

      • name - 驱动程序的显示名称

      • version - 驱动程序的版本

      • source - 驱动程序的来源

      • entrypoint - 如果需要非默认入口点,则为驱动程序使用的入口点

      • driver - 驱动程序共享库的路径

    • 如果未找到注册表键,则搜索目录 %LOCALAPPDATA%\ADBC\Drivers

  5. 如果设置了 LOAD_FLAG_SEARCH_SYSTEM 加载选项,驱动管理器将搜索系统级配置。

    • 在注册表中搜索键 HKEY_LOCAL_MACHINE\SOFTWARE\ADBC\Drivers\${name}。如果存在,则使用与上述相同的子键。