2 个版本
0.1.1 | 2022 年 11 月 17 日 |
---|---|
0.1.0 | 2022 年 11 月 17 日 |
#2699 在 数据库接口
在 lunatic-db 中使用
345KB
6.5K SLoC
mysql
此 crate 提供
- 纯 Rust 的 MySql 数据库驱动程序;
- 连接池。
特性
- 支持 macOS、Windows 和 Linux;
- 通过 nativetls 或 rustls 支持 TLS(请参阅 SSL 支持 部分);
- 支持 MySql 文本协议,即支持简单文本查询和文本结果集;
- 支持 MySql 二进制协议,即支持预编译语句和二进制结果集;
- 支持多结果集;
- 支持预编译语句的命名参数(请参阅 命名参数 部分);
- 可选的每连接预编译语句缓存(请参阅 语句缓存 部分);
- 缓冲池(请参阅 缓冲池 部分);
- 支持大于 2^24 的 MySql 数据包;
- 支持 Unix 套接字和 Windows 命名管道;
- 支持自定义 LOCAL INFILE 处理程序;
- 支持 MySql 协议压缩;
- 支持身份验证插件
- mysql_native_password - 用于 Mysql v8 之前的版本;
- caching_sha2_password - 用于 Mysql v8 及更高版本。
安装
将 crate 的所需版本放入您的 dependencies
部分的 Cargo.toml
文件中
[dependencies]
mysql = "*"
示例
use mysql::*;
use mysql::prelude::*;
#[derive(Debug, PartialEq, Eq)]
struct Payment {
customer_id: i32,
amount: i32,
account_name: Option<String>,
}
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let url = "mysql://root:password@localhost:3307/db_name";
# Opts::try_from(url)?;
# let url = get_opts();
let pool = Pool::new(url)?;
let mut conn = pool.get_conn()?;
// Let's create a table for payments.
conn.query_drop(
r"CREATE TEMPORARY TABLE payment (
customer_id int not null,
amount int not null,
account_name text
)")?;
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()) },
];
// Now let's insert payments to the database
conn.exec_batch(
r"INSERT INTO payment (customer_id, amount, account_name)
VALUES (:customer_id, :amount, :account_name)",
payments.iter().map(|p| params! {
"customer_id" => p.customer_id,
"amount" => p.amount,
"account_name" => &p.account_name,
})
)?;
// Let's select payments from database. Type inference should do the trick here.
let selected_payments = conn
.query_map(
"SELECT customer_id, amount, account_name from payment",
|(customer_id, amount, account_name)| {
Payment { customer_id, amount, account_name }
},
)?;
// Let's make sure, that `payments` equals to `selected_payments`.
// Mysql gives no guaranties on order of returned rows
// without `ORDER BY`, so assume we are lucky.
assert_eq!(payments, selected_payments);
println!("Yay!");
Ok(())
}
crate 特性
-
crate 的特性
- buffer-pool(默认启用) - 启用缓冲池(请参阅 缓冲池 部分)
-
默认启用的外部特性
-
对于
flate2
crate(请查阅flate2
crate 文档了解可用的特性)- flate2/zlib(必需) - 默认选择
zlib
后端。
- flate2/zlib(必需) - 默认选择
-
对于
mysql_common
crate(请查阅mysql_common
crate 文档了解可用的特性)- mysql_common/bigdecimal03 – 默认启用
bigdecimal03
- mysql_common/rust_decimal – 默认启用
rust_decimal
- mysql_common/time03 – 默认启用
time03
- mysql_common/uuid – 默认启用
uuid
- mysql_common/frunk – 默认启用
frunk
- mysql_common/bigdecimal03 – 默认启用
-
请注意,如果您正在使用 default-features = false
,则需要重新启用外部功能。
[dependencies]
# Lets say that we want to use the `rustls-tls` feature:
mysql = { version = "*", default-features = false, features = ["rustls-tls", "buffer-pool"] }
# Previous line disables default mysql features,
# so now we have to choose the flate2 backend (this is necessary),
# as well as the desired set of mysql_common features:
flate2 = { version = "*", default-features = false, features = ["zlib"] }
mysql_common = { version = "*", default-features = false, features = ["bigdecimal03", "time03", "uuid"]}
API 文档
请参阅crate 文档。
基本结构
Opts
此结构包含服务器主机名、客户端用户名/密码以及其他控制客户端行为的设置。
基于 URL 的连接字符串
请注意,您可以使用基于 URL 的连接字符串作为 Opts
实例的源。URL 架构必须是 mysql
。主机、端口、凭据以及查询参数应按照 RFC 3986 规范提供。
示例
let _ = Opts::from_url("mysql://127.0.0.1/some_db")?;
let _ = Opts::from_url("mysql://[::1]/some_db")?;
let _ = Opts::from_url("mysql://user:pass%[email protected]:3307/some_db?")?;
支持的 URL 参数(有关每个字段的含义,请参阅创建 API 文档中 Opts
结构的文档)
prefer_socket: true | false
- 定义Opts
结构中相同字段的值;tcp_keepalive_time_ms: u32
- 定义Opts
结构中tcp_keepalive_time
字段的值(以毫秒为单位);tcp_keepalive_probe_interval_secs: u32
- 定义tcp_keepalive_probe_interval_secs
字段的值;tcp_keepalive_probe_count: u32
- 定义tcp_keepalive_probe_count
字段的值;tcp_connect_timeout_ms: u64
- 定义tcp_connect_timeout
字段的值(以毫秒为单位);tcp_user_timeout_ms
- 定义tcp_user_timeout
字段的值(以毫秒为单位);stmt_cache_size: u32
- 定义Opts
结构中相同字段的值;compress
- 定义Opts
结构中相同字段的值。支持值包括true
- 启用默认压缩级别的压缩;fast
- 启用“快速”压缩级别的压缩;best
- 启用“最佳”压缩级别的压缩;1
..9
- 启用给定压缩级别的压缩。
socket
- UNIX 上的套接字路径或在 Windows 上的管道名称。
OptsBuilder
它是 Opts
结构的方便构建器。它定义了 Opts
结构字段的设置器。
let opts = OptsBuilder::new()
.user(Some("foo"))
.db_name(Some("bar"));
let _ = Conn::new(opts)?;
Conn
此结构表示一个活动的 MySql 连接。它还包含语句缓存和最后一个结果集的元数据。
Conn 的析构函数将优雅地将其从服务器断开连接。
事务
它是 START TRANSACTION
开始并在 COMMIT
或 ROLLBACK
结束的例程的简单包装。
use mysql::*;
use mysql::prelude::*;
let pool = Pool::new(get_opts())?;
let mut conn = pool.get_conn()?;
let mut tx = conn.start_transaction(TxOpts::default())?;
tx.query_drop("CREATE TEMPORARY TABLE tmp (TEXT a)")?;
tx.exec_drop("INSERT INTO tmp (a) VALUES (?)", ("foo",))?;
let val: Option<String> = tx.query_first("SELECT a from tmp")?;
assert_eq!(val.unwrap(), "foo");
// Note, that transaction will be rolled back implicitly on Drop, if not committed.
tx.rollback();
let val: Option<String> = conn.query_first("SELECT a from tmp")?;
assert_eq!(val, None);
池
它是连接池的引用,可以克隆并在线程之间共享。
use mysql::*;
use mysql::prelude::*;
use std::thread::spawn;
let pool = Pool::new(get_opts())?;
let handles = (0..4).map(|i| {
spawn({
let pool = pool.clone();
move || {
let mut conn = pool.get_conn()?;
conn.exec_first::<u32, _, _>("SELECT ? * 10", (i,))
.map(Option::unwrap)
}
})
});
let result: Result<Vec<u32>> = handles.map(|handle| handle.join().unwrap()).collect();
assert_eq!(result.unwrap(), vec![0, 10, 20, 30]);
语句
实际上,声明(Statement)只是一个标识符与声明元数据的结合,即关于其参数和列的信息。在内部,Statement
结构还持有支持命名参数所需的其他数据(见下文)。
use mysql::*;
use mysql::prelude::*;
let pool = Pool::new(get_opts())?;
let mut conn = pool.get_conn()?;
let stmt = conn.prep("DO ?")?;
// The prepared statement will return no columns.
assert!(stmt.columns().is_empty());
// The prepared statement have one parameter.
let param = stmt.params().get(0).unwrap();
assert_eq!(param.schema_str(), "");
assert_eq!(param.table_str(), "");
assert_eq!(param.name_str(), "?");
值(Value)
此枚举表示MySQL单元格的原始值。库提供了通过下文描述的FromValue
特质在不同rust类型之间的转换。
FromValue
特质
此特质由mysql_common
创建重新导出。请参阅其crate文档以获取支持的转换列表。
特质提供两种转换风格
-
from_value(Value) -> T
- 方便,但可能会引发恐慌的转换。注意,对于
Value
的任何变体,都存在一个类型,该类型完全覆盖其域,即对于Value
的任何变体,都存在T: FromValue
,使得from_value
永远不会引发恐慌。这意味着,如果您的数据库模式是已知的,那么您可以使用from_value
来编写应用程序,而无需担心运行时恐慌。 -
from_value_opt(Value) -> Option<T>
- 非恐慌,但不太方便的转换。此函数对于在源数据库模式未知的情况下测试转换非常有用。
use mysql::*;
use mysql::prelude::*;
let via_test_protocol: u32 = from_value(Value::Bytes(b"65536".to_vec()));
let via_bin_protocol: u32 = from_value(Value::UInt(65536));
assert_eq!(via_test_protocol, via_bin_protocol);
let unknown_val = // ...
// Maybe it is a float?
let unknown_val = match from_value_opt::<f64>(unknown_val) {
Ok(float) => {
println!("A float value: {}", float);
return Ok(());
}
Err(FromValueError(unknown_val)) => unknown_val,
};
// Or a string?
let unknown_val = match from_value_opt::<String>(unknown_val) {
Ok(string) => {
println!("A string value: {}", string);
return Ok(());
}
Err(FromValueError(unknown_val)) => unknown_val,
};
// Screw this, I'll simply match on it
match unknown_val {
val @ Value::NULL => {
println!("An empty value: {:?}", from_value::<Option<u8>>(val))
},
val @ Value::Bytes(..) => {
// It's non-utf8 bytes, since we already tried to convert it to String
println!("Bytes: {:?}", from_value::<Vec<u8>>(val))
}
val @ Value::Int(..) => {
println!("A signed integer: {}", from_value::<i64>(val))
}
val @ Value::UInt(..) => {
println!("An unsigned integer: {}", from_value::<u64>(val))
}
Value::Float(..) => unreachable!("already tried"),
val @ Value::Double(..) => {
println!("A double precision float value: {}", from_value::<f64>(val))
}
val @ Value::Date(..) => {
use time::PrimitiveDateTime;
println!("A date value: {}", from_value::<PrimitiveDateTime>(val))
}
val @ Value::Time(..) => {
use std::time::Duration;
println!("A time value: {:?}", from_value::<Duration>(val))
}
}
行(Row)
在内部,Row
是Value
的向量,它还允许通过列名/偏移量进行索引,并存储行元数据。库提供了通过下文描述的FromRow
特质在Row
和Rust类型序列之间的转换。
FromRow
特质
此特质由mysql_common
创建重新导出。请参阅其crate文档以获取支持的转换列表。
此转换基于FromValue
,因此具有两种类似的风格
from_row(Row) -> T
- 与from_value
相同,但用于行;from_row_opt(Row) -> Option<T>
- 与from_value_opt
相同,但用于行。
Queryable
特质提供了基于此特质的查询结果行的隐式转换。
use mysql::*;
use mysql::prelude::*;
let mut conn = Conn::new(get_opts())?;
// Single-column row can be converted to a singular value:
let val: Option<String> = conn.query_first("SELECT 'foo'")?;
assert_eq!(val.unwrap(), "foo");
// Example of a mutli-column row conversion to an inferred type:
let row = conn.query_first("SELECT 255, 256")?;
assert_eq!(row, Some((255u8, 256u16)));
// The FromRow trait does not support to-tuple conversion for rows with more than 12 columns,
// but you can do this by hand using row indexing or `Row::take` method:
let row: Row = conn.exec_first("select 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12", ())?.unwrap();
for i in 0..row.len() {
assert_eq!(row[i], Value::Int(i as i64));
}
// Another way to handle wide rows is to use HList (requires `mysql_common/frunk` feature)
use frunk::{HList, hlist, hlist_pat};
let query = "select 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15";
type RowType = HList!(u8, u16, u32, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8);
let first_three_columns = conn.query_map(query, |row: RowType| {
// do something with the row (see the `frunk` crate documentation)
let hlist_pat![c1, c2, c3, ...] = row;
(c1, c2, c3)
});
assert_eq!(first_three_columns.unwrap(), vec![(0_u8, 1_u16, 2_u32)]);
// Some unknown row
let row: Row = conn.query_first(
// ...
# "SELECT 255, Null",
)?.unwrap();
for column in row.columns_ref() {
// Cells in a row can be indexed by numeric index or by column name
let column_value = &row[column.name_str().as_ref()];
println!(
"Column {} of type {:?} with value {:?}",
column.name_str(),
column.column_type(),
column_value,
);
}
参数(Params)
表示预处理语句的参数,但此类型不会直接出现在您的代码中,因为二进制协议API会请求T: Into<Params>
,其中Into<Params>
被实现
-
对于
Into<Value>
类型的元组,最多可达12个参数;注意:单元素元组需要额外的逗号,例如
("foo",)
; -
对于当您的语句包含超过12个参数的情况,可以使用
IntoIterator<Item: Into<Value>>
。 -
对于命名参数表示(下面将描述的
params!
宏的值)。
use mysql::*;
use mysql::prelude::*;
let mut conn = Conn::new(get_opts())?;
// Singular tuple requires extra comma:
let row: Option<u8> = conn.exec_first("SELECT ?", (0,))?;
assert_eq!(row.unwrap(), 0);
// More than 12 parameters:
let row: Option<u8> = conn.exec_first(
"SELECT CONVERT(? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ?, UNSIGNED)",
(0..16).collect::<Vec<_>>(),
)?;
assert_eq!(row.unwrap(), 120);
注意:请参考 mysql_common crate 文档,了解实现 Into<Value>
的类型列表。
Serialized
,Deserialized
用于在需要为JSON单元格提供值或需要将JSON单元格解析为结构体时的情况的包装结构。
use mysql::*;
use mysql::prelude::*;
/// Serializable structure.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Example {
foo: u32,
}
// Value::from for Serialized will emit json string.
let value = Value::from(Serialized(Example { foo: 42 }));
assert_eq!(value, Value::Bytes(br#"{"foo":42}"#.to_vec()));
// from_value for Deserialized will parse json string.
let structure: Deserialized<Example> = from_value(value);
assert_eq!(structure, Deserialized(Example { foo: 42 }));
QueryResult
这是一个支持多结果集的查询结果行的迭代器。它旨在在需要结果集迭代期间完全控制的情况下使用。对于其他情况,Queryable
提供了一组方法,这些方法将立即消耗第一个结果集并丢弃所有其他内容。
此迭代器是惰性的,因此它不会在您迭代之前从服务器读取结果。MySql协议是严格顺序的,所以 Conn
将在结果完全消耗之前可变借用(请参阅 QueryResult::iter
文档)。
use mysql::*;
use mysql::prelude::*;
let mut conn = Conn::new(get_opts())?;
// This query will emit two result sets.
let mut result = conn.query_iter("SELECT 1, 2; SELECT 3, 3.14;")?;
let mut sets = 0;
while let Some(result_set) = result.iter() {
sets += 1;
println!("Result set columns: {:?}", result_set.columns());
println!(
"Result set meta: {}, {:?}, {} {}",
result_set.affected_rows(),
result_set.last_insert_id(),
result_set.warnings(),
result_set.info_str(),
);
for row in result_set {
match sets {
1 => {
// First result set will contain two numbers.
assert_eq!((1_u8, 2_u8), from_row(row?));
}
2 => {
// Second result set will contain a number and a float.
assert_eq!((3_u8, 3.14), from_row(row?));
}
_ => unreachable!(),
}
}
}
assert_eq!(sets, 2);
文本协议
MySql文本协议实现在 Queryable::query*
方法集中。当您的查询没有参数时很有用。
注意:服务器将文本协议结果集中的所有值编码为字符串,因此 from_value
转换可能会导致额外的解析成本。
示例
let pool = Pool::new(get_opts())?;
let val = pool.get_conn()?.query_first("SELECT POW(2, 16)")?;
// Text protocol returns bytes even though the result of POW
// is actually a floating point number.
assert_eq!(val, Some(Value::Bytes("65536".as_bytes().to_vec())));
TextQuery
特性。
TextQuery
特性从查询的角度覆盖了 Queryable::query*
方法的集合,即 TextQuery
是在某些合适连接给出时可以执行的操作。合适的连接有
&池
Conn
PooledConn
&mutConn
&mutPooledConn
&mut事务
此特性的独特之处在于,您可以放弃连接并因此生成满足 'static
的 QueryResult
。
use mysql::*;
use mysql::prelude::*;
fn iter(pool: &Pool) -> Result<impl Iterator<Item=Result<u32>>> {
let result = "SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3".run(pool)?;
Ok(result.map(|row| row.map(from_row)))
}
let pool = Pool::new(get_opts())?;
let it = iter(&pool)?;
assert_eq!(it.collect::<Result<Vec<u32>>>()?, vec![1, 2, 3]);
二进制协议和预编译语句。
MySql二进制协议实现在 prep
,close
和在 Queryable
特性上定义的 exec*
方法集中。预编译语句是将Rust值传递给MySql服务器的唯一方法。MySql使用 ?
符号作为参数占位符,并且只能在预期单个MySql值的位置使用参数。例如
let pool = Pool::new(get_opts())?;
let val = pool.get_conn()?.exec_first("SELECT POW(?, ?)", (2, 16))?;
assert_eq!(val, Some(Value::Double(65536.0)));
语句
在MySql中,每个预编译语句都属于特定的连接,并且不能在其他连接上执行。这样做会导致错误。驱动程序不会以任何方式将语句与其连接绑定,但可以通过查看包含在 Statement
结构中的连接ID来观察连接ID。
let pool = Pool::new(get_opts())?;
let mut conn_1 = pool.get_conn()?;
let mut conn_2 = pool.get_conn()?;
let stmt_1 = conn_1.prep("SELECT ?")?;
// stmt_1 is for the conn_1, ..
assert!(stmt_1.connection_id() == conn_1.connection_id());
assert!(stmt_1.connection_id() != conn_2.connection_id());
// .. so stmt_1 will execute only on conn_1
assert!(conn_1.exec_drop(&stmt_1, ("foo",)).is_ok());
assert!(conn_2.exec_drop(&stmt_1, ("foo",)).is_err());
语句缓存
Conn
将在客户端管理预编译语句的缓存,因此后续使用相同语句的调用不会导致客户端-服务器往返。每个连接的缓存大小由 stmt_cache_size
字段确定,该字段位于 Opts
结构中。超出此边界的语句将按 LRU 顺序关闭。
如果 stmt_cache_size
为零,则完全禁用语句缓存。
注意事项
-
禁用语句缓存意味着您必须自己使用
Conn::close
关闭语句,否则它们会耗尽服务器限制/资源; -
您应该注意 MySql 服务器的
max_prepared_stmt_count
选项。如果活动连接数乘以stmt_cache_size
的值大于该值,则在准备另一个语句时可能会收到错误。
命名参数
MySql 本身不支持命名参数,因此在客户端实现。应使用 :name
作为命名参数的占位符语法。命名参数采用以下命名约定
- 参数名称必须以
_
或a..z
开始 - 参数名称可以继续使用
_
、a..z
和0..9
命名参数可以在语句中重复,例如 SELECT :foo, :foo
将需要一个重复的单个命名参数 foo
,该参数将在执行语句时重复在相应的位置。
应使用 params!
宏来构建执行参数。
注意:在单个语句中不能混合位置参数和命名参数。
示例
let pool = Pool::new(get_opts())?;
let mut conn = pool.get_conn()?;
let stmt = conn.prep("SELECT :foo, :bar, :foo")?;
let foo = 42;
let val_13 = conn.exec_first(&stmt, params! { "foo" => 13, "bar" => foo })?.unwrap();
// Short syntax is available when param name is the same as variable name:
let val_42 = conn.exec_first(&stmt, params! { foo, "bar" => 13 })?.unwrap();
assert_eq!((foo, 13, foo), val_42);
assert_eq!((13, foo, 13), val_13);
缓冲池
Crate 使用全局无锁缓冲池用于 IO 和数据序列化/反序列化,这有助于避免基本场景的分配。您可以使用以下环境变量来控制其特性
-
RUST_MYSQL_BUFFER_POOL_CAP
(默认为 128)- 控制池容量。如果池已满,则丢弃的缓冲区将被立即重新分配。将其设置为0
以在运行时禁用池。 -
RUST_MYSQL_BUFFER_SIZE_CAP
(默认为 4MiB)- 控制存储在池中的缓冲区的最大容量。当缓冲区返回到池时,丢弃的缓冲区容量将缩小到该值。
要完全禁用池(例如,您正在使用 jemalloc),请从默认 crate 特性集中删除 buffer-pool
特性(请参阅 Crate Features 部分)。
BinQuery
和 BatchQuery
特性。
BinQuery
和 BatchQuery
特性涵盖了从查询的角度来看 Queryable::exec*
方法的集合,即 BinQuery
是在提供适当的连接时可以执行的操作(请参阅 TextQuery
部分以获取适合连接的列表)。
与 TextQuery
一样,您可以放弃连接并获取满足 'static
的 QueryResult
。
BinQuery
用于预处理语句,预处理语句需要一组参数,因此 BinQuery
为 QueryWithParams
结构体实现,可以通过 WithParams
特性来获取。
示例
use mysql::*;
use mysql::prelude::*;
let pool = Pool::new(get_opts())?;
let result: Option<(u8, u8, u8)> = "SELECT ?, ?, ?"
.with((1, 2, 3)) // <- WithParams::with will construct an instance of QueryWithParams
.first(&pool)?; // <- QueryWithParams is executed on the given pool
assert_eq!(result.unwrap(), (1, 2, 3));
BatchQuery
特性是批量执行语句的辅助工具。它在 QueryWithParams
上实现,其中参数是一个参数迭代器。
use mysql::*;
use mysql::prelude::*;
let pool = Pool::new(get_opts())?;
let mut conn = pool.get_conn()?;
"CREATE TEMPORARY TABLE batch (x INT)".run(&mut conn)?;
"INSERT INTO batch (x) VALUES (?)"
.with((0..3).map(|x| (x,))) // <- QueryWithParams constructed with an iterator
.batch(&mut conn)?; // <- batch execution is preformed here
let result: Vec<u8> = "SELECT x FROM batch".fetch(conn)?;
assert_eq!(result, vec![0, 1, 2]);
Queryable
Queryable
特性定义了 Conn
、PooledConn
和 Transaction
的通用方法。基本方法包括
query_iter
- 执行文本查询并获取QueryResult
的基本方法;prep
- 准备语句的基本方法;exec_iter
- 执行语句并获取QueryResult
的基本方法;close
- 关闭语句的基本方法;
该特性还定义了一组基于基本方法的辅助方法。这些方法将仅消耗第一个结果集,其他结果集将被丢弃
{query|exec}
- 将结果收集到Vec<T: FromRow>
中;{query|exec}_first
- 获取第一个T: FromRow
,如果有的话;{query|exec}_map
- 将每个T: FromRow
映射到某个U
;{query|exec}_fold
- 将T: FromRow
的集合折叠到单个值;{query|exec}_drop
- 立即丢弃结果。
该特性还定义了 exec_batch
函数,它是批量执行语句的辅助工具。
SSL 支持
SSL 支持有两种形式
-
基于 native-tls – 这是默认选项,通常无需考虑陷阱(参见
native-tls
crate 功能)。 -
基于 rustls – 使用 Rust 编写的 TLS 后端。请使用
rustls-tls
crate 功能来启用它(参见 Crate Features 部分)。关于 rustls 的几点注意事项
- 如果您尝试通过 IP 地址连接到服务器,它将失败,需要主机名;
- 它很可能在 Windows 上不起作用,至少在使用默认服务器证书的情况下,这些证书由 MySql 安装程序生成。
变更日志
在此处提供 这里
许可证
根据以下其中之一许可
- Apache License, Version 2.0, (LICENSE-APACHE 或 https://www.apache.org/licenses/LICENSE-2.0)
- 麻省理工学院许可协议(LICENSE-MIT 或 https://opensource.org/licenses/MIT)
任选其一。
贡献
除非您明确说明,否则根据Apache-2.0许可协议定义的,您有意提交以包含在工作中的任何贡献,将如上双许可,不附加任何额外条款或条件。
依赖项
~12–17MB
~335K SLoC