#odbc #macro #proc #proc-macro #handle #error #rs-odbc

rs-odbc_derive

为rs-odbc crate提供过程宏

1个不稳定版本

0.1.0 2021年8月22日

#25 in #odbc


用于 rs-odbc

Apache-2.0

10KB
185

RS-ODBC

License: Apache 2.0 Build Crates.io

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.alibltdl.a。**Windows不支持静态链接**。

API差异

  1. ODBC函数作为句柄的方法或相关函数实现。因此,提供句柄标识符(例如SQL_HANDLE_STMT)作为参数变得不再必要。
C ODBC示例 Rust ODBC示例
SQLHSTMT hstmt = SQL_NULL_HSTMT;
int ret1 = SQLAllocHandle(
    SQL_HANDLE_STMT,
    hdbc,
    &hstmt
);
if (SQL_SUCCEEDED(ret1)) {
    int ret2 = SQLCancelHandle(
        SQL_HANDLE_STMT,
        hstmt
    );
}
let (hstmt, ret1) = SQLHSTMT::SQLAllocHandle(&hdbc);
if SQL_SUCCEEDED(ret1) {
    let hstmt = hstmt.unwrap();
    let ret2 = hstmt.SQLCancelHandle();
}
  1. 大多数ODBC句柄方法按照标准返回SQLRETURN,但一些将返回一个元组(Result<<succ_handle_type>, <err_handle_type>>, SQLRETURN)(例如SQLDriverConnect)。返回句柄使得在Rust的类型系统中实现ODBC状态转换FSM成为可能。

  2. 接受指针及其长度的ODBC函数使用切片引用。切片引用防止应用程序编写者写入/读取超出分配单元末尾的可能性。

C ODBC示例 Rust ODBC示例
SQLCHAR catalog_name[] = "rs_odbc";
int ret = SQLSetConnAttr(
    hdbc,
    SQL_ATTR_CURRENT_CATALOG,
    catalog_name,
    SQL_NTS,
);
let catalog_name = "rs_odbc";
let ret = hdbc.SQLSetConnAttr(
    SQL_ATTR_CURRENT_CATALOG,
    catalog_name.as_ref()
);
  1. ODBC版本在分配环境句柄时定义。通常,这应该是您ODBC应用程序的第一步,但在Rust中由类型系统处理。
C ODBC示例 Rust ODBC示例
SQLHENV henv = SQL_NULL_HENV;
int ret1 = SQLAllocHandle(
    SQL_HANDLE_ENV,
    &SQL_NULL_HANDLE,
    &henv
);
if (SQL_SUCCEEDED(ret1)) {
    int ret2 = SQLSetEnvAttr(
        henv,
        SQL_ATTR_ODBC_VERSION,
        SQL_OV_ODBC3_80,
        0
    );
}
let (env, ret1) = SQLHENV::SQLAllocHandle(&SQL_NULL_HANDLE);
if (SQL_SUCCEEDED(ret1)) {
    let env: SQLHENV<SQL_OV_ODBC3_80> = env.unwrap();
}
  1. 在作用域结束时自动断开连接和释放句柄。

未初始化的变量

当使用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::newMaybeUninit::zeroed**来最小化UB的风险。

线程安全

所有句柄都是 Send,然而,目前,只有 SQLHENVSync,因为跨线程共享其他句柄的引用被认为是一种反模式。显然,要取消在另一个线程上运行的连接或语句句柄上的函数,必须能够在线程之间共享句柄引用。由于取消操作被ODBC标准定义为始终是线程安全的操作,对于这种特定场景,您可以从原始句柄派生出实现 Sync 特性的句柄,例如 WeakSQLHSTMTRefSQLHSTMT。以 Ref 为前缀的句柄是从您的原始句柄的引用分配的,而以 Weak 为前缀的句柄是从包装在 Arc 中的原始句柄分配的。

// TODO: Add code example

如果您有一个希望在线程之间共享除 SQLHENV 之外的句柄的场景,请提交一个描述您的用例的问题。

不安全API

有些情况下,无法通过类型系统确保安全性。在这些罕见的情况下,您可以分配 UnsafeSQLHSTMTUnsafeSQLHDESC,它们实现了一些语句函数的不安全API。

// TODO:

测试

集成测试使用已经设置好数据库和ODBC驱动程序的docker化环境。

可以使用 docker-compose up -d 设置测试环境。
使用 docker exec -t rs-odbc sh -lc 'cargo test' 执行测试。

  • 使用 RUSTFLAGS=-Awarnings 来抑制编译器警告,这些警告会导致编译测试失败。

依赖关系

~1.5MB
~35K SLoC