2 个不稳定版本
0.2.0 | 2022年10月9日 |
---|---|
0.1.0 | 2021年8月22日 |
#5 in #odbc
495KB
10K SLoC
RS-ODBC
看起来和感觉像 ODBC 但更安全的 ODBC API 的 Rust 实现
描述
该软件包的主要设计目标是,在可能的情况下提供类型安全,同时使暴露的 API 尽可能接近原始 ODBC API。该软件包防止了 C 代码中固有的大多数安全问题,并将大多数应用程序错误移动到编译时。ODBC 状态转换有限状态机 (FSM) 在 Rust 的类型系统中实现,从而防止了许多无效句柄错误成为编译错误。
为什么选择这个软件包
1. 知名的 API
如果您已经使用过 ODBC API,您会发现在使用此软件包时会感到熟悉,即您不需要再学习另一个 API。使用此软件包,您将获得一个广为人知、高度使用和标准化的 API。对原始 ODBC API 的抽象级别非常低,因此您可以使用原始 ODBC 文档。将现有的 ODBC 应用程序或示例从 C 转换为 Rust 非常简单。
2. 安全的 API
对于大多数应用程序,您将永远不会需要使用原始指针。暴露 ODBC 之上的自定义高级 API 封装器的软件包可能迫使您在需要使用它们不支持的 API 表达的 ODBC 功能时回退到使用原始 API。除非这些软件包是在这个软件包之上构建的,否则这将为您的应用程序引入不必要的风险。
3. 完整的 API
该软件包被设计为完全符合 ODBC 标准,因此不应该有任何低级 ODBC 功能不能通过该软件包表达。但是,可能尚未实现某个特定功能。如果您发现某个功能缺失,请鼓励您打开一个要求该功能的 Issue。
安装
要使用此库,您必须在您的宿主操作系统上安装和配置 ODBC 驱动程序管理器。此库在 Windows 上动态链接到 odbc32.dll
,在 Linux 和 OS-X 上链接到 libodbc.so
(unixODBC)。要启用原生库的静态链接,请使用 cargo 的 static
功能。
Cargo 功能
static
启用本地库的静态链接。如果启用了静态链接,用户必须定义 RS_ODBC_LINK_SEARCH
环境变量,其中包含此crate将要链接的静态库的路径。对于unixODBC,用户应在此路径下提供 libodbc.a
和 libltdl.a
。 Windows 不支持静态链接。
API 差异
- ODBC 函数作为句柄的方法或关联函数实现。因此,将句柄标识符(例如
SQL_HANDLE_STMT
)作为参数传递变得不再必要
C ODBC 示例 | Rust ODBC 示例 |
---|---|
|
|
-
大多数ODBC句柄方法按照标准返回
SQLRETURN
,但某些会返回一个元组(Result<<succ_handle_type>, <err_handle_type>>, SQLRETURN)
(例如 SQLDriverConnect)。返回句柄使得在 Rust 的类型系统中实现 ODBC 状态转换 FSM 成为可能 -
接受指针及其长度的 ODBC 函数使用切片的引用。切片引用防止了应用程序编写者超出分配单元末尾写入/读取的可能性。
C ODBC 示例 | Rust ODBC 示例 |
---|---|
|
|
- ODBC 版本在分配环境句柄时定义。通常,这应该是您的 ODBC 应用程序的第一步,但在 Rust 中这由类型系统处理
C ODBC 示例 | Rust ODBC 示例 |
---|---|
|
|
- 在作用域结束时自动断开连接和释放句柄
未初始化的变量
当使用 ODBC 函数(如 SQLGetEnvAttr
)时,这些函数接受可变引用并将其写入,但驱动程序或 DM 永远不会从它们读取,则不需要初始化这些变量,因为它们将在调用相关 ODBC 函数期间进行初始化。为了避免不必要的初始化,许多通过 Rust 公开的 ODBC 函数允许使用已初始化和未初始化的变量(通过 MaybeUninit
)
use core::mem::MaybeUninit;
use rs_odbc::api::Allocate;
use rs_odbc::env::{self, SQL_ATTR_CONNECTION_POOLING, SQL_OV_ODBC3_80};
use rs_odbc::handle::{SQLHENV, SQL_NULL_HANDLE};
fn main() {
let (env, _) = SQLHENV::SQLAllocHandle(&SQL_NULL_HANDLE);
let env: SQLHENV<SQL_OV_ODBC3_80> = env.unwrap();
let mut value = env::SQL_CP_ONE_PER_HENV; // Initialized to default value
let _ = env.SQLGetEnvAttr(SQL_ATTR_CONNECTION_POOLING, Some(&mut value), None);
// Confirm value was modified by the driver
assert_ne!(env::SQL_CP_ONE_PER_HENV, value);
let mut value = MaybeUninit::uninit(); // Variable is uninitialized
let _ = env.SQLGetEnvAttr(SQL_ATTR_CONNECTION_POOLING, Some(&mut value), None);
// Value initialized by the driver
match unsafe { value.assume_init() } {
env::SQL_CP_ONE_PER_DRIVER => println!("SQL_CP_ONE_PER_DRIVER"),
env::SQL_CP_ONE_PER_HENV => println!("SQL_CP_ONE_PER_HENV"),
env::SQL_CP_DRIVER_AWARE => println!("SQL_CP_DRIVER_AWARE"),
env::SQL_CP_OFF => println!("SQL_CP_OFF"),
_ => panic!("Driver returned unknown value"),
}
}
**强烈不建议使用未初始化的变量**,因为它们的使用通常是一种微优化,对代码的性能没有可测量的影响,并且如果处理不当会引入潜在的不确定行为(如部分初始化的变量)。如果某些 ODBC 函数只能接收未初始化的参数,**建议用户使用 MaybeUninit::new
或 MaybeUninit::zeroed
** 来最小化不确定行为的风险。
线程安全
**所有句柄都是 Send
**,然而,目前,**只有 SQLHENV
是 Sync
**,因为跨线程共享其他句柄的引用被认为是反模式。显然,要取消在另一个线程上运行的连接或语句句柄上的函数,必须能够跨线程共享句柄引用。由于**取消操作由 ODBC 标准定义为始终是线程安全的操作**,对于这个特定场景,您可以从原始句柄推导出一个实现 Sync
特性的句柄,如 WeakSQLHSTMT
或 RefSQLHSTMT
。以 Ref
前缀的句柄是从原始句柄的引用分配的,而以 Weak
前缀的句柄是从您的原始句柄包装在 Arc
中的对象分配的。
// TODO: Add code example
如果您有需要在不同线程之间共享除SQLHENV
之外的处理句柄的使用场景,请提交一个问题,描述您的使用场景。
不安全的API
在某些情况下,无法通过类型系统保证安全性。在这些罕见的情况下,您可以分配UnsafeSQLHSTMT
和UnsafeSQLHDESC
,这些类型实现了额外的非安全API,使得一些语句函数变得不安全。
// TODO:
测试
集成测试使用已经配置了数据库和ODBC驱动的docker环境。
可以通过docker-compose up -d
设置测试环境
测试使用docker exec -t rs-odbc sh -lc 'cargo test'
执行
- 使用
RUSTFLAGS=-Awarnings
来关闭编译器警告,这些警告可能导致编译测试失败
依赖项
~250–690KB
~16K SLoC