65 个不稳定版本
| 0.34.2 | 2024 年 8 月 13 日 |
|---|---|
| 0.34.1 | 2024 年 3 月 20 日 |
| 0.33.0 | 2023 年 11 月 8 日 |
| 0.32.2 | 2023 年 4 月 22 日 |
| 0.2.0 | 2016 年 11 月 17 日 |
#22 在 数据库接口
92,077 每月下载量
用于 59 个 Crates (42 个直接使用)
440KB
9K SLoC
mysql_async
Rust 编程语言的基于 Tokio 的异步 MySql 客户端库。
安装
该库托管在 crates.io。
[dependencies]
mysql_async = "<desired version>"
包功能
默认功能集很广 - 它包括所有默认的 mysql_common 功能 以及基于 native-tls 的 TLS 支持。
功能列表
-
minimal– 启用必要的功能(目前唯一必要的功能是flate2后端)。启用- `flate2/zlib"
示例
[dependencies] mysql_async = { version = "*", default-features = false, features = ["minimal"]} -
minimal-rust- 与minimal相同,但选择基于 rust 的 flate2 后端。启用flate2/rust_backend
-
default– 启用以下功能集minimalnative-tls-tlsbigdecimalrust_decimaltimefrunkbinlog
-
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包启用仪器。主要操作(
query、prepare、exec)在INFO级别上进行跟踪。其余操作,包括get_conn,在DEBUG级别上进行跟踪。在DEBUG级别下,还将SQL查询和参数添加到query、prepare和exec跨度中。此外,一些内部查询在TRACE级别上进行跟踪。示例
[dependencies] mysql_async = { version = "*", features = ["tracing"] } -
binlog- 启用binlog相关功能。启用- `mysql_common/binlog"
代理功能
derive– 启用mysql_common/derive功能chrono= 启用mysql_common/chrono功能time= 启用mysql_common/time功能bigdecimal= 启用mysql_common/bigdecimal功能rust_decimal= 启用mysql_common/rust_decimal功能frunk= 启用mysql_common/frunk功能
TLS/SSL 支持
SSL支持有两种形式
-
基于native-tls – 这是默认选项,通常无需担心(参见
native-tls-tls包功能)。 -
基于rustls – Rust编写的TLS后端(参见
rustls-tls包功能)。请注意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是一个智能指针 – 每个clone都将指向同一个池实例。Pool是Send + Sync + 'static– 可以自由传递它。- 使用
Pool::disconnect来优雅地关闭池。 - ⚠️
Pool::new是懒惰的,不会断言服务器可用性。
事务
Conn::start_transaction是一个包装器,它以START TRANSACTION开始,并以COMMIT或ROLLBACK结束。
如果未显式提交或回滚已删除的事务,它将隐式回滚。请注意,此行为将由池(在conn删除时)或下一个查询触发,即可能延迟。
API不允许您运行嵌套事务,因为某些语句会导致隐式提交(例如START TRANSACTION),因此这种行为被选择为更不容易出错。
值
此枚举表示MySQL单元格的原始值。库通过下文描述的FromValue特质提供了Value与不同的Rust类型之间的转换。
FromValue特质
此特质由mysql_common创建重新导出。有关支持转换的列表,请参阅其crate文档。
特质提供两种转换方式
-
from_value(Value) -> T- 方便,但可能导致恐慌的转换。请注意,对于
Value的任何变体,都存在一个完全覆盖其域的类型,即对于Value的任何变体,都存在T: FromValue,使得from_value永远不会恐慌。这意味着,如果您的数据库模式是已知的,那么可以编写只使用from_value的应用程序,而不必担心运行时恐慌。此外,即使类型看起来足够,某些转换也可能失败,例如在无效日期的情况下(参见sql模式)。
-
from_value_opt(Value) -> Option<T>- 非恐慌,但不太方便的转换。此函数在源数据库模式未知的情况下进行转换时很有用。
MySQL查询协议
文本协议
MySQL文本协议实现在Queryable::query*方法集和特质中,如果查询是prelude::Queryprelude::AsQuery。当您的查询没有参数时很有用。
注意:服务器将文本协议结果集中的所有值都编码为字符串,因此from_value转换可能导致额外的解析成本。
二进制协议和预编译语句。
MySQL二进制协议实现在exec*方法集中,这些方法定义在prelude::Queryable特质和特质中,如果查询是prelude::QueryQueryWithParams。预编译语句是将Rust值传递给MySQL服务器的唯一方式。MySQL使用?符号作为参数占位符。
注意:只能使用期望单个 MySql 值的参数,即您不能使用类似以下操作:SELECT ... WHERE id IN ? 将向量作为参数执行。您需要构建类似以下查询:SELECT ... WHERE id IN (?, ?, ...) 并将每个向量元素作为参数传递。
命名参数
MySql 本身不支持命名参数,因此在客户端实现。应使用 :name 作为命名参数的占位符语法。命名参数使用以下命名约定
- 参数名称必须以
_或a..z开头 - 参数名称可以继续使用
_、a..z和0..9
注意:这些规则意味着,例如,语句 SELECT :fooBar 将被转换为 SELECT ?Bar,所以请小心。
命名参数可以在语句中重复,例如 SELECT :foo, :foo 将需要一个名为 foo 的单个命名参数,该参数将在执行语句时在相应位置重复。
应使用 params! 宏来构建执行参数。
注意:单个语句中不能混合位置参数和命名参数。
语句
在 MySql 中,每个预处理语句属于特定的连接,不能在另一个连接上执行。尝试这样做将导致错误。驱动程序不会以任何方式将语句绑定到其连接,但可以查看包含在 Statement 结构中的连接 ID。
LOCAL INFILE 处理程序
警告:您应了解有关 LOAD DATA LOCAL 的安全注意事项。
LOCAL INFILE 处理程序有两种类型——全局 和 本地。
如果服务器发出 LOCAL INFILE 请求,驱动程序将尝试为其找到一个处理程序
- 如果连接上有任何本地处理程序,它将尝试使用该本地处理程序;
- 如果指定了任何全局处理程序,它将尝试使用通过
OptsBuilder::local_infile_handler指定,它将尝试使用该全局处理程序; - 如果没有找到任何处理程序,它将发出
LocalInfileError::NoHandler。
处理程序(本地 或 全局)的目的是返回 InfileData。
全局 LOCAL INFILE 处理程序
简单来说,全局处理器是一个异步函数,它接受一个文件名(作为 &[u8])并返回 Result<InfileData>。
您可以使用 OptsBuilder::local_infile_handler 来设置它。如果没有为连接安装本地处理器,则服务器将使用它。此处理器可能会被调用多次。
示例
WhiteListFsHandler是一个 全局 处理器。- 每个
T: Fn(&[u8]) -> BoxFuture<'static, Result<InfileData, LocalInfileError>>都是 全局 处理器。
本地 本地 INFILE 处理器。
简单来说,本地处理器是一个返回 Result<InfileData> 的未来。
这是一个一次性处理器 - 使用后会被消耗。您可以使用 Conn::set_infile_handler 来设置它。此处理器优先于 全局 处理器。
值得注意
impl Drop for Conn将清除本地处理器,即处理器将在连接返回到Pool时被移除。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:password@127.0.0.1:3307/mysqlCOMPRESS- 设置为1或true以启用测试的压缩SSL- 设置为1或true以启用测试的 TLS
您可以使用 Docker 运行测试服务器。请注意,有关最大允许数据包、本地 INFILE 和二进制日志的参数对于正确运行测试是必需的(请参阅 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
变更日志
可在 此处 查看
许可证
许可协议为以下之一
- Apache License, Version 2.0, (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- 麻省理工学院许可证(LICENSE-MIT 或 https://open-source.org.cn/licenses/MIT)
任选其一。
贡献
除非您明确表示,否则根据Apache-2.0许可证定义,您有意提交给作品的所有贡献,应按上述方式双许可,不附加任何额外条款或条件。
依赖关系
~16–34MB
~559K SLoC