3 个版本

0.32.4 2023 年 8 月 13 日
0.32.3 2023 年 8 月 13 日
0.32.2 2023 年 8 月 13 日

#1205异步


用于 orm_mysql

MIT/Apache

415KB
8K SLoC

Gitter

Build Status API Documentation on docs.rs

mysql_async

基于 Tokio 的 Rust 编程语言的异步 MySql 客户端库。

安装

该库托管在 crates.io 上。

[dependencies]
mysql_async = "<desired version>"

crate 特性

默认特性集较广 - 包括所有默认的 mysql_common 特性 以及基于 native-tls 的 TLS 支持。

特性列表

  • minimal – 启用必要的特性(目前唯一的必要特性是 flate2 后端)。启用

    • `flate2/zlib"

    示例

    [dependencies]
    mysql_async = { version = "*", default-features = false, features = ["minimal"]}
    

    *注意:可以通过直接选择它来使用另一个 flate2 后端

    [dependencies]
    mysql_async = { version = "*", default-features = false }
    flate2 = { version = "*", default-features = false, features = ["rust_backend"] }
    
  • default – 启用以下 crate 和依赖项的特性

    • native-tls-tls
    • `flate2/zlib"
    • mysql_common/bigdecimal03
    • mysql_common/rust_decimal
    • mysql_common/time03
    • mysql_common/uuid
    • mysql_common/frunk
  • default-rustls – 与默认设置相同,但使用 rustls-tls 而不是 native-tls-tls

    示例

    [dependencies]
    mysql_async = { version = "*", default-features = false, features = ["default-rustls"] }
    
  • native-tls-tls – 启用基于 native-tls 的 TLS 支持 (与 rustls-tls 冲突)

    示例

    [dependencies]
    mysql_async = { version = "*", default-features = false, features = ["native-tls-tls"] }
    
    
  • rustls-tls – 启用基于 native-tls 的 TLS 支持 (与 native-tls-tls 冲突)

    示例

    [dependencies]
    mysql_async = { version = "*", default-features = false, features = ["rustls-tls"] }
    
    
  • tracing – 启用通过 tracing 包进行仪表化。

    主要操作(queryprepareexec)在INFO级别进行了监控。其余操作,包括get_conn,在DEBUG级别进行监控。在DEBUG级别,还将SQL查询和参数添加到queryprepareexec跨度中。还有一些内部查询在TRACE级别进行了监控。

    示例

    [dependencies]
    mysql_async = { version = "*", features = ["tracing"] }
    
  • derive – 启用mysql_common/derive功能

TLS/SSL支持

SSL支持有两种形式

  1. 基于native-tls – 这是默认选项,通常无需特别处理(请参阅native-tls-tls crate功能)。

  2. 基于rustls – Rust编写的TLS后端(请参阅rustls-tls crate功能)。

    请注意关于rustls的几点

    • 如果您尝试通过IP地址连接到服务器,它将失败,需要主机名;
    • 它可能在Windows上不起作用,至少在使用MySql安装程序生成的默认服务器证书的情况下。

连接URL参数

驱动程序支持一组URL参数(请参阅Opts上的文档)。

示例

use mysql_async::prelude::*;

#[derive(Debug, PartialEq, Eq, Clone)]
struct Payment {
    customer_id: i32,
    amount: i32,
    account_name: Option<String>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let payments = vec![
        Payment { customer_id: 1, amount: 2, account_name: None },
        Payment { customer_id: 3, amount: 4, account_name: Some("foo".into()) },
        Payment { customer_id: 5, amount: 6, account_name: None },
        Payment { customer_id: 7, amount: 8, account_name: None },
        Payment { customer_id: 9, amount: 10, account_name: Some("bar".into()) },
    ];

    let database_url = /* ... */
    # get_opts();

    let pool = mysql_async::Pool::new(database_url);
    let mut conn = pool.get_conn().await?;

    // Create a temporary table
    r"CREATE TEMPORARY TABLE payment (
        customer_id int not null,
        amount int not null,
        account_name text
    )".ignore(&mut conn).await?;

    // Save payments
    r"INSERT INTO payment (customer_id, amount, account_name)
      VALUES (:customer_id, :amount, :account_name)"
        .with(payments.iter().map(|payment| params! {
            "customer_id" => payment.customer_id,
            "amount" => payment.amount,
            "account_name" => payment.account_name.as_ref(),
        }))
        .batch(&mut conn)
        .await?;

    // Load payments from the database. Type inference will work here.
    let loaded_payments = "SELECT customer_id, amount, account_name FROM payment"
        .with(())
        .map(&mut conn, |(customer_id, amount, account_name)| Payment { customer_id, amount, account_name })
        .await?;

    // Dropped connection will go to the pool
    drop(conn);

    // The Pool must be disconnected explicitly because
    // it's an asynchronous operation.
    pool.disconnect().await?;

    assert_eq!(loaded_payments, payments);

    // the async fn returns Result, so
    Ok(())
}

Pool结构是一个异步连接池。

请注意

  • Pool是一个智能指针 – 每个克隆都将指向同一个池实例。
  • PoolSend + Sync + 'static – 可以自由传递。
  • 使用Pool::disconnect优雅地关闭池。
  • ⚠️ Pool::new是懒加载的,不会断言服务器可用性。

事务

Conn::start_transaction是一个包装器,以START TRANSACTION开始,以COMMITROLLBACK结束。

未提交的事务将隐式回滚,除非显式提交或回滚。请注意,此行为将由池(在conn drop时)或下一个查询触发,即可能会延迟。

API不允许您运行嵌套事务,因为某些语句会导致隐式提交(START TRANSACTION是其中之一),因此选择了这种更不容易出错的行为。

此枚举表示MySql单元格的原始值。库通过以下描述的FromValue特征在不同rust类型之间提供转换。

FromValue特征

此特征从mysql_common创建中重新导出。请参阅其crate文档以获取支持的转换列表。

特征提供两种转换方式

  • from_value(Value) -> T - 方便,但可能引发panic的转换。

    请注意,对于任何变体的Value,都存在一种类型,可以完全覆盖其域,即对于任何变体的Value,都存在T: FromValue,使得from_value永远不会崩溃。这意味着,如果您的数据库模式是已知的,那么您可以使用仅使用from_value的应用程序,而不必担心运行时崩溃。

    另外请注意,即使类型看起来足够,某些转换也可能失败,例如在无效日期的情况下(参见sql模式)。

  • from_value_opt(Value) -> Option<T> - 非崩溃但不太方便的转换。

    此函数在源数据库模式未知的情况下,用于探查转换。

MySql查询协议

文本协议

MySql文本协议实现了一组Queryable::query*方法,以及当查询是prelude::AsQuery时,在prelude::Query特质中。当您的查询没有参数时,这非常有用。

注意:文本协议结果集的所有值都将由服务器编码为字符串,因此from_value转换可能会导致额外的解析成本。

二进制协议和预处理语句。

MySql二进制协议实现了一组在prelude::Queryable特质上定义的exec*方法,以及当查询是QueryWithParams时,在prelude::Query特质中。预处理语句是向MySql服务器传递Rust值的唯一方式。MySql使用?符号作为参数占位符。

注意:只能在期望单个MySql值的地方使用参数,即您不能像下面这样使用向量作为参数执行SELECT ... WHERE id IN ?。您需要构建一个查询,其外观类似于SELECT ... WHERE id IN (?, ?, ...),并将每个向量元素作为参数传递。

命名参数

MySql本身不支持命名参数,因此这是在客户端实现的。应使用:name作为命名参数的占位符语法。命名参数使用以下命名约定

  • 参数名必须以_a..z之一开头
  • 参数名称可以继续使用 _a..z0..9

注意: 这条规则意味着,例如,语句 SELECT :fooBar 将被翻译为 SELECT ?Bar,所以请小心。

命名参数可以在语句中重复,例如 SELECT :foo, :foo 需要单个命名参数 foo,该参数将在语句执行期间在相应位置重复。

应使用 params! 宏来构建执行参数。

注意: 单个语句中不能混合位置参数和命名参数。

语句

在 MySQL 中,每个预编译语句都属于特定的连接,不能在另一个连接上执行。尝试这样做会导致错误。驱动程序不会以任何方式将语句与其连接绑定,但可以通过包含在 Statement 结构中的连接 id 来查看。

LOCAL INFILE 处理程序

警告: 您应该注意 LOAD DATA LOCAL 的安全考虑

LOCAL INFILE 处理程序有两种类型——全局局部

如果服务器请求 LOCAL INFILE,驱动程序将尝试找到相应的处理程序

  1. 如果连接上安装了任何 局部 处理程序,它将尝试使用该处理程序;
  2. 如果指定了任何 全局 处理程序,将通过 OptsBuilder::local_infile_handler,它将尝试使用该处理程序;
  3. 如果没有找到任何处理程序,将发出 LocalInfileError::NoHandler

处理程序(局部全局)的目的是返回 InfileData

全局 LOCAL INFILE 处理程序

参见 prelude::GlobalHandler

简而言之,全局 处理程序是一个异步函数,它接受一个文件名(作为 &[u8])并返回 Result<InfileData>

您可以使用 OptsBuilder::local_infile_handler 来设置它。如果连接上没有安装任何 局部 处理程序,服务器将使用它。这个处理程序可能会被多次调用。

示例

  1. WhiteListFsHandler 是一个 全局 处理程序。
  2. 每个 T: Fn(&[u8]) -> BoxFuture<'static, Result<InfileData, LocalInfileError>> 是一个 全局 处理器。

局部 本地文件处理程序。

简而言之,局部处理程序是一个返回 Result<InfileData> 的未来。

这是一个一次性处理程序 - 使用后将被消耗。您可以使用 Conn::set_infile_handler 来设置它。此处理程序具有比 全局 处理程序更高的优先级。

值得注意

  1. impl Drop for Conn 将清除 局部 处理程序,即当连接返回到 Pool 时,将移除处理程序。
  2. Conn::reset 将清除 局部 处理程序。

示例

#
let pool = mysql_async::Pool::new(database_url);

let mut conn = pool.get_conn().await?;
"CREATE TEMPORARY TABLE tmp (id INT, val TEXT)".ignore(&mut conn).await?;

// We are going to call `LOAD DATA LOCAL` so let's setup a one-time handler.
conn.set_infile_handler(async move {
    // We need to return a stream of `io::Result<Bytes>`
    Ok(stream::iter([Bytes::from("1,a\r\n"), Bytes::from("2,b\r\n3,c")]).map(Ok).boxed())
});

let result = r#"LOAD DATA LOCAL INFILE 'whatever'
    INTO TABLE `tmp`
    FIELDS TERMINATED BY ',' ENCLOSED BY '\"'
    LINES TERMINATED BY '\r\n'"#.ignore(&mut conn).await;

match result {
    Ok(()) => (),
    Err(Error::Server(ref err)) if err.code == 1148 => {
        // The used command is not allowed with this MySQL version
        return Ok(());
    },
    Err(Error::Server(ref err)) if err.code == 3948 => {
        // Loading local data is disabled;
        // this must be enabled on both the client and the server
        return Ok(());
    }
    e @ Err(_) => e.unwrap(),
}

// Now let's verify the result
let result: Vec<(u32, String)> = conn.query("SELECT * FROM tmp ORDER BY id ASC").await?;
assert_eq!(
    result,
    vec![(1, "a".into()), (2, "b".into()), (3, "c".into())]
);

drop(conn);
pool.disconnect().await?;

测试

测试使用以下环境变量

  • DATABASE_URL - 默认为 mysql://root:[email protected]:3307/mysql
  • COMPRESS - 设置为 1true 以启用测试的压缩
  • SSL - 设置为 1true 以启用测试的 TLS

您可以使用 docker 运行测试服务器。请注意,测试运行需要正确设置最大允许的数据包、本地文件和二进制日志相关的参数(请参阅 azure-pipelines.yml

docker run -d --name container \
    -v `pwd`:/root \
    -p 3307:3306 \
    -e MYSQL_ROOT_PASSWORD=password \
    mysql:8.0 \
    --max-allowed-packet=36700160 \
    --local-infile \
    --log-bin=mysql-bin \
    --log-slave-updates \
    --gtid_mode=ON \
    --enforce_gtid_consistency=ON \
    --server-id=1

变更日志

在此处 available

许可

根据您的选择,许可协议为以下之一

贡献

除非您明确声明,否则根据 Apache-2.0 许可证定义的,您有意提交以包含在作品中的任何贡献,均应按照上述方式双重许可,不附加任何额外的条款或条件。

依赖项

~19–36MB
~620K SLoC