launchbadge/sqlx

GitHub: launchbadge/sqlx

SQLx 是一个异步、纯 Rust 的 SQL 工具包,通过编译时检查查询且无需 DSL 来解决 Rust 开发中数据库交互的类型安全与性能问题。

Stars: 16787 | Forks: 1577

SQLx

🧰 Rust SQL 工具包

actions status Crates.io version chat docs.rs docs Download

安装 | 用法 | 文档 | 生态系统 | Discord


LaunchBadge 团队 用 ❤️ 构建

有疑问?请务必先查看 FAQ!

SQLx 是一个异步、纯 Rust SQL crate,其特色是在无需 DSL 的情况下支持编译时检查查询。 - **真正的异步**。从头开始使用 async/await 构建,以实现最大的并发性。 - **编译时检查查询**(如果你需要的话)。参见 [SQLx 不是 ORM](#sqlx-is-not-an-orm)。 - **数据库无关**。支持 [PostgreSQL], [MySQL], [MariaDB], [SQLite]。 - 在 0.7 版本之前曾支持 [MSSQL],但作为我们 [SQLx Pro 计划] 的一部分,在完全重写驱动程序之前已被移除。 - **纯 Rust**。Postgres 和 MySQL/MariaDB 驱动程序均使用 **零** unsafe†† 代码以纯 Rust 编写。 - **运行时无关**。适用于不同的运行时([`async-std`] / [`tokio`] / [`actix`])和 TLS 后端([`native-tls`], [`rustls`])。 † SQLite 驱动程序使用 libsqlite3 C 库,因为 SQLite 是一个嵌入式数据库(如果我们想在 SQLite 上实现纯 Rust,唯一的办法就是将 SQLite 的_所有_代码移植到 Rust)。 †† 除非启用了 `sqlite` 功能,否则 SQLx 使用 `#![forbid(unsafe_code)]`。 SQLite 驱动程序通过 `libsqlite3-sys` 直接调用 SQLite3 API,这需要 `unsafe`。 - 跨平台。作为原生 Rust,SQLx 可以在支持 Rust 的任何地方编译。 - 使用 `sqlx::Pool` 的内置连接池。 - 行流。数据从数据库异步读取并按需解码。 - 自动语句准备和缓存。使用高级查询 API(`sqlx::query`)时,语句会针对每个连接进行准备和缓存。 - 简单(未准备)查询执行,包括将结果提取到高级 API 使用的相同 `Row` 类型中。支持批量执行并返回所有语句的结果。 - 在支持的数据库([MySQL], [MariaDB] 和 [PostgreSQL])中使用传输层安全 (TLS)。 - [PostgreSQL] 使用 `LISTEN` 和 `NOTIFY` 进行异步通知。 - 支持保存点的嵌套事务。 - `Any` 数据库驱动程序,用于在运行时更改数据库驱动程序。`AnyPool` 连接到由 URL 方案指示的驱动程序。 ## 安装 SQLx 兼容 [`async-std`]、[`tokio`] 和 [`actix`] 运行时;以及 [`native-tls`] 和 [`rustls`] TLS 后端。添加依赖项时,你必须选择一个 `runtime` + `tls` 的运行时功能。 ``` # Cargo.toml [dependencies] # 从以下选项中选择一个: # tokio(无 TLS) sqlx = { version = "0.8", features = [ "runtime-tokio" ] } # tokio + native-tls sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-native-tls" ] } # tokio + rustls 与 ring 和 WebPKI CA 证书 sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring-webpki" ] } # tokio + rustls 与 ring 和平台原生 CA 证书 sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring-native-roots" ] } # tokio + rustls 与 aws-lc-rs sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-aws-lc-rs" ] } # async-std(无 TLS) sqlx = { version = "0.8", features = [ "runtime-async-std" ] } # async-std + native-tls sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-native-tls" ] } # async-std + rustls 与 ring 和 WebPKI CA 证书 sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-ring-webpki" ] } # async-std + rustls 与 ring 和平台原生 CA 证书 sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-ring-native-roots" ] } # async-std + rustls 与 aws-lc-rs sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-aws-lc-rs" ] } ``` #### Cargo 功能标志 出于向后兼容的原因,运行时和 TLS 功能可以作为一个单独的功能一起选择,也可以分开选择。 为了向前兼容,你应该使用单独的运行时和 TLS 功能,因为组合功能将来可能会被移除。 - `runtime-async-std`:使用 `async-std` 运行时,但不启用 TLS 后端。 - `runtime-tokio`:使用 `tokio` 运行时,但不启用 TLS 后端。 - Actix-web 与 Tokio 完全兼容,因此不再需要单独的运行时功能。 - `tls-native-tls`:使用 `native-tls` TLS 后端(*nix 上为 OpenSSL,Windows 上为 SChannel,macOS 上为 Secure Transport)。 - `tls-rustls`:使用 `rustls` TLS 后端(跨平台后端,仅支持 TLS 1.2 和 1.3)。 - `postgres`:添加对 Postgres 数据库服务器的支持。 - `mysql`:添加对 MySQL/MariaDB 数据库服务器的支持。 - `mssql`:添加对 MSSQL 数据库服务器的支持。 - `sqlite`:添加对独立的 [SQLite](https://sqlite.org/) 数据库引擎的支持,包含捆绑并静态链接的 SQLite。 - `sqlite-unbundled`:与上述 (`sqlite`) 相同,但从系统链接 SQLite 而不是使用捆绑版本。 * 允许独立于 SQLx 更新 SQLite 或使用分支版本。 * 你必须在系统上安装 SQLite,或在构建时提供库的路径。 详情请参见 [`rusqlite` README](https://github.com/rusqlite/rusqlite?tab=readme-ov-file#notes-on-building-rusqlite-and-libsqlite3-sys)。 * 如果 SQLite 版本太旧,可能会导致链接错误。建议使用 `3.20.0` 或更高版本。 * 由于使用 bindgen,可能会增加构建时间。 - `sqlite-preupdate-hook`:启用 SQLite 的 [preupdate hook](https://sqlite.org/c3ref/preupdate_count.html) API。 * 作为一个单独的功能公开,因为它通常默认不启用。 * 如果系统 SQLite 版本不支持,将 `sqlite-unbundled` 与此功能一起使用可能会导致链接器失败。 - `any`:添加对 `Any` 数据库驱动程序的支持,该驱动程序可以在运行时代理到数据库驱动程序。 - `derive`:添加对 derive 系列宏的支持,包括 `FromRow`、`Type`、`Encode`、`Decode`。 - `macros`:添加对 `query*!` 宏的支持,允许进行编译时检查查询。 - `migrate`:添加对迁移管理和 `migrate!` 宏的支持,允许编译时嵌入式迁移。 - `uuid`:添加对 UUID 的支持。 - `chrono`:添加对来自 `chrono` 的日期和时间类型的支持。 - `time`:添加对来自 `time` crate 的日期和时间类型的支持(`chrono` 的替代方案,如果同时启用,`query!` 宏优先使用此功能)。 - `bstr`:添加对 `bstr::BString` 的支持。 - `bigdecimal`:添加使用 `bigdecimal` crate 对 `NUMERIC` 的支持。 - `rust_decimal`:添加使用 `rust_decimal` crate 对 `NUMERIC` 的支持。 - `ipnet`:添加使用 `ipnet` crate 对 `INET` 和 `CIDR`(在 postgres 中)的支持。 - `ipnetwork`:添加使用 `ipnetwork` crate 对 `INET` 和 `CIDR`(在 postgres 中)的支持。 - `json`:添加使用 `serde_json` crate 对 `JSON` 和 `JSONB`(在 postgres 中)的支持。 - 离线模式现已始终启用。参见 [sqlx-cli/README.md][readme-offline]。 ## SQLx 不是 ORM! SQLx 支持**编译时检查查询**。然而,它并不是通过提供用于构建查询的 Rust API 或 DSL(领域特定语言)来实现的。相反,它提供了一些宏,这些宏接受常规 SQL 作为输入,并确保它对你的数据库有效。其工作原理是 SQLx 在编译时连接到你的开发数据库,让数据库本身验证(并返回有关信息)你的 SQL 查询。这有一些可能令人惊讶的含义: - 由于 SQLx 从不必自己解析 SQL 字符串,因此可以使用开发数据库接受的任何语法(包括数据库扩展添加的内容) - 由于数据库允许你检索有关查询的信息量不同,你从查询宏获得的 SQL 验证程度取决于数据库 **如果你正在寻找(异步)ORM,** 可以查看我们新的 [生态系统 wiki 页面](https://github.com/launchbadge/sqlx/wiki/Ecosystem#orms)! ## 用法 请参阅 `examples/` 文件夹以获取更深入的用法。 ### 快速入门 ``` use sqlx::postgres::PgPoolOptions; // use sqlx::mysql::MySqlPoolOptions; // etc. #[async_std::main] // Requires the `attributes` feature of `async-std` // or #[tokio::main] // or #[actix_web::main] async fn main() -> Result<(), sqlx::Error> { // Create a connection pool // for MySQL/MariaDB, use MySqlPoolOptions::new() // for SQLite, use SqlitePoolOptions::new() // etc. let pool = PgPoolOptions::new() .max_connections(5) .connect("postgres://postgres:password@localhost/test").await?; // Make a simple query to return the given parameter (use a question mark `?` instead of `$1` for MySQL/MariaDB) let row: (i64,) = sqlx::query_as("SELECT $1") .bind(150_i64) .fetch_one(&pool).await?; assert_eq!(row.0, 150); Ok(()) } ``` ### 连接 可以使用任何数据库连接类型并调用 `connect()` 来建立单个连接。 ``` use sqlx::Connection; let conn = SqliteConnection::connect("sqlite::memory:").await?; ``` 通常,你会希望改为创建一个连接池(`sqlx::Pool`),以便应用程序调节其使用的服务器端连接数量。 ``` let pool = MySqlPool::connect("mysql://user:pass@host/database").await?; ``` ### 查询 在 SQL 中,查询可以分为准备好的(参数化)或未准备好的(简单)。准备好的查询会将其查询计划_缓存_,使用二进制通信模式(带宽更低,解码更快),并利用参数来避免 SQL 注入。未准备好的查询很简单,仅适用于准备好的语句不起作用的情况,例如各种数据库命令(例如 `PRAGMA` 或 `SET` 或 `BEGIN`)。 SQLx 支持这两种查询类型的所有操作。在 SQLx 中,`&str` 被视为未准备好的查询,而 `Query` 或 `QueryAs` 结构体被视为准备好的查询。 ``` // low-level, Executor trait conn.execute("BEGIN").await?; // unprepared, simple query conn.execute(sqlx::query("DELETE FROM table")).await?; // prepared, cached query ``` 我们应该尽可能使用高级 `query` 接口。为了简化这一点,该类型上有终结器,以避免需要使用执行器进行包装。 ``` sqlx::query("DELETE FROM table").execute(&mut conn).await?; sqlx::query("DELETE FROM table").execute(&pool).await?; ``` `execute` 查询终结器返回受影响的行数(如果有),并丢弃所有接收到的结果。 此外,还有 `fetch`、`fetch_one`、`fetch_optional` 和 `fetch_all` 用于接收结果。 从 `sqlx::query` 返回的 `Query` 类型将从数据库返回 `Row<'conn>`。可以使用 `row.get()` 按顺序或按名称访问列值。由于 `Row` 保留对连接的不可变借用,因此一次只能存在一个 `Row`。 `fetch` 查询终结器返回一个类似流的类型,该类型遍历结果集中的行。 ``` // provides `try_next` use futures_util::TryStreamExt; // provides `try_get` use sqlx::Row; let mut rows = sqlx::query("SELECT * FROM users WHERE email = ?") .bind(email) .fetch(&mut conn); while let Some(row) = rows.try_next().await? { // map the row into a user-defined domain type let email: &str = row.try_get("email")?; } ``` 为了协助将行映射到域类型,可以使用以下两种习语之一: ``` let mut stream = sqlx::query("SELECT * FROM users") .map(|row: PgRow| { // map the row into a user-defined domain type }) .fetch(&mut conn); ``` ``` #[derive(sqlx::FromRow)] struct User { name: String, id: i64 } let mut stream = sqlx::query_as::<_, User>("SELECT * FROM users WHERE email = ? OR name = ?") .bind(user_email) .bind(user_name) .fetch(&mut conn); ``` 我们可以使用 `fetch_one` 或 `fetch_optional` 来请求数据库中的一个必需或可选结果,而不是结果流。 ### 编译时验证 我们可以使用宏 `sqlx::query!` 对 SQL 进行编译时语法和语义验证,并输出到一个匿名记录类型,其中每个 SQL 列都是一个 Rust 字段(在需要时使用原始标识符)。 ``` let countries = sqlx::query!( " SELECT country, COUNT(*) as count FROM users GROUP BY country WHERE organization = ? ", organization ) .fetch_all(&pool) // -> Vec<{ country: String, count: i64 }> .await?; // countries[0].country // countries[0].count ``` 与 `query()` 的区别: - 输入(或绑定)参数必须一次性全部给出(并且它们在编译时被验证为正确的数量和类型)。 - 输出类型是匿名记录。在上面的例子中,该类型类似于: { country: String, count: i64 } - 必须在构建时将 `DATABASE_URL` 环境变量设置为一个可以对其进行查询准备的数据库;该数据库不必包含任何数据,但必须是相同的类型(MySQL、Postgres 等),并且具有与你将在运行时连接的数据库相同的架构。 为了方便起见,你可以使用 [一个 `.env` 文件][dotenv]1 来设置 DATABASE_URL,这样你就不必每次都传递它: DATABASE_URL=mysql://localhost/my_database `query!()` 的最大缺点是无法命名输出类型(因为 Rust 官方不支持匿名记录)。为了解决这个问题,有一个 `query_as!()` 宏,它基本相同,只是你可以命名输出类型。 ``` // no traits are needed struct Country { country: String, count: i64 } let countries = sqlx::query_as!(Country, " SELECT country, COUNT(*) as count FROM users GROUP BY country WHERE organization = ? ", organization ) .fetch_all(&pool) // -> Vec .await?; // countries[0].country // countries[0].count ``` 为了避免即使在未进行任何修改(针对访问数据库的代码部分)时也需要周围有一个开发数据库来编译项目,你可以启用“离线模式”以使用 `sqlx` 命令行工具缓存 SQL 查询分析的结果。参见 [sqlx-cli/README.md](./sqlx-cli/README.md#enable-building-in-offline-mode-with-query)。 编译时验证的查询在编译时会做相当多的工作。通过在你的 `Cargo.toml` 中添加以下内容,使用优化构建时,像 `cargo check` 和 `cargo build` 这样的增量操作可能会明显加快(更多信息请参阅 Cargo 手册的 [Profiles 部分](https://doc.rust-lang.org/cargo/reference/profiles.html)) ``` [profile.dev.package.sqlx-macros] opt-level = 3 ``` 1 `dotenv` crate 本身自 [2021 年 12 月](https://github.com/dotenv-rs/dotenv/issues/74) 起似乎已被废弃,因此我们现在改用 `dotenvy` crate。文件格式是一样的。 ## 安全性 此 crate 使用 `#![forbid(unsafe_code)]` 来确保所有内容都以 100% 的安全 Rust 实现。 如果启用了 `sqlite` 功能,这将降级为 `#![deny(unsafe_code)]`,并在 `sqlx::sqlite` 模块上使用 `#![allow(unsafe_code)]`。我们在几个地方与 C SQLite API 进行交互。我们尝试记录我们假设的每个调用的不变量。我们绝对欢迎对我们的 unsafe 代码使用情况进行审计和反馈。 ## 许可证 根据以下任一许可证授权 - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) 或 http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) 或 http://opensource.org/licenses/MIT) 你可任选其一。 ## 贡献 你明确声明,否则根据 Apache-2.0 许可证的定义,你故意提交以包含在作品中的任何贡献均应按上述方式双重许可,无需任何附加条款或条件。
标签:Crates.io, ORM, PostgreSQL, Rust, SQL, SQLite, SQLx, Toolkit, 云安全监控, 可视化界面, 后端开发, 异步, 数据库, 无DSL, 测试用例, 类型安全, 系统审计, 编译时检查, 网络流量审计, 静态分析, 驱动程序