1个不稳定版本
0.1.0 | 2021年8月22日 |
---|
#25 in #odbc
用于 rs-odbc
10KB
185 行
RS-ODBC
Rust实现ODBC API,其外观和感觉与ODBC相同,但更安全
描述
此crate的主要设计目标是,在可能的情况下提供类型安全,同时使暴露的API尽可能接近原始ODBC API。此crate防止了C代码固有的大多数安全问题的发生,并将大多数应用程序错误移至编译时。ODBC状态转换FSM是在Rust的类型系统中实现的,这样就可以防止许多无效句柄错误在编译时发生。
为什么使用这个crate
1. 已知API
如果您已经使用过ODBC API,那么使用此crate会感到很熟悉,即 您无需学习另一个API。使用此crate,您将获得一个广为人知、高度使用且标准化的API。此crate对原始ODBC API的抽象级别很低,因此您可以使用原始ODBC文档。将现有的ODBC应用程序或示例从C转换为Rust非常简单。
2. 安全API
对于大多数应用程序 您将永远不需要使用原始指针。暴露ODBC自定义高级API包装器的crate很可能会迫使您在使用它们提供的API无法表达ODBC功能时回退到使用原始API。除非这些crate基于此crate构建,否则这将为您的应用程序引入不必要的风险。
3. 完整API
此crate旨在完全符合ODBC规范,因此不应有任何低级ODBC功能无法通过此crate表达。但是,某个特定功能可能尚未实现。如果您注意到缺少某个功能,请鼓励您提出一个要求实现该功能的问题。
安装
要使用这个库,您必须在主机操作系统上安装并配置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"),
}
}
**强烈不建议使用未初始化的变量**,因为它们的使用通常是微优化,不会对您的代码性能产生可测量的影响,并且如果不小心(如部分初始化的变量)会引入潜在的不确定行为(UB)。如果某些ODBC函数只能接收未初始化的参数,**建议用户使用MaybeUninit::new
或MaybeUninit::zeroed
**来最小化UB的风险。
线程安全
所有句柄都是 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
来抑制编译器警告,这些警告会导致编译测试失败。
依赖关系
~1.5MB
~35K SLoC