#sql-database #database-driver #sql-query #postgresql #sqlite #sqlx #mysql

sqlx-etorreborre

🧰 Rust SQL 工具集。一个异步、纯 Rust SQL 库,支持编译时检查查询而不使用 DSL。支持 PostgreSQL、MySQL 和 SQLite。

6 个版本

0.7.9 2024年7月3日
0.7.8 2024年7月3日

#305数据库接口

Download history 709/week @ 2024-06-28 739/week @ 2024-07-05 72/week @ 2024-07-12 16/week @ 2024-07-19 35/week @ 2024-07-26 2/week @ 2024-08-02

每月 69 次下载
用于 2 crates

MIT/Apache

1MB
14K SLoC

SQLx

🧰 Rust SQL 工具集


LaunchBadge 团队 用 ❤️ 构建

有问题吗?请先 查看常见问题解答

SQLx 是一个异步、纯 Rust SQL 库,支持编译时检查查询而不使用 DSL。

  • 真正异步。从头开始使用 async/await 以实现最大并发。

  • 编译时检查查询(如果需要)。请参阅 SQLx 不是 ORM

  • 数据库无关。支持 PostgreSQLMySQLMariaDBSQLite

    • 版本 0.7 之前支持 MSSQL,但由于 SQLx Pro 初始化中的驱动程序全面重写,现已移除。
  • 纯 Rust。Postgres 和 MySQL/MariaDB 驱动程序使用纯 Rust 编写,使用 不安全††代码。

  • 运行时无关。在不同的运行时(async-std / tokio / actix)和 TLS 后端(native-tlsrustls)上工作。

† SQLite 驱动程序使用 libsqlite3 C 库作为 SQLite 是一个嵌入式数据库(如果我们想使 SQLite 纯 Rust,唯一的方法是将 所有 SQLite 移植到 Rust)。

†† SQLx 除非启用了 sqlite 功能,否则使用 #![forbid(unsafe_code)]。SQLite 驱动程序直接通过 libsqlite3-sys 调用 SQLite3 API,这需要 unsafe


  • 跨平台。作为原生 Rust,SQLx 将在任何支持 Rust 的地方编译。

  • 内置连接池,使用 sqlx::Pool

  • 行流。数据异步地从数据库中读取,并在需要时进行解码。

  • 自动语句准备和缓存。当使用高级查询 API(sqlx::query)时,语句按连接进行准备和缓存。

  • 简单的(未准备)查询执行,包括将结果检索到与高级 API 使用的相同 Row 类型中。支持批量执行并从所有语句返回结果。

  • 在支持的情况下使用传输层安全性 (TLS)(MySQLMariaDBPostgreSQL)。

  • 使用 LISTENNOTIFY 进行异步通知,以支持 PostgreSQL

  • 支持保存点的嵌套事务。

  • Any 数据库驱动程序,可以在运行时更改数据库驱动程序。一个 AnyPool 连接到 URL 方案指示的驱动程序。

安装

SQLx 与 async-stdtokioactix 运行时兼容;以及 native-tlsrustls TLS 后端。添加依赖项时,必须选择一个具有 runtime + tls 的运行时功能。

# Cargo.toml
[dependencies]
# PICK ONE OF THE FOLLOWING:

# tokio (no TLS)
sqlx = { version = "0.7", features = [ "runtime-tokio" ] }
# tokio + native-tls
sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-native-tls" ] }
# tokio + rustls
sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-rustls" ] }

# async-std (no TLS)
sqlx = { version = "0.7", features = [ "runtime-async-std" ] }
# async-std + native-tls
sqlx = { version = "0.7", features = [ "runtime-async-std", "tls-native-tls" ] }
# async-std + rustls
sqlx = { version = "0.7", features = [ "runtime-async-std", "tls-rustls" ] }

Cargo 功能标志

出于向后兼容性原因,运行时和 TLS 功能可以作为一个单独的功能选择,也可以单独选择。

为了向前兼容性,应使用单独的运行时和 TLS 功能,因为组合功能可能在将来被删除。

  • runtime-async-std:使用 async-std 运行时而不启用 TLS 后端。

  • runtime-async-std-native-tls:使用 async-std 运行时和 native-tls TLS 后端(已软弃用)。

  • runtime-async-std-rustls:使用 async-std 运行时和 rustls TLS 后端(已软弃用)。

  • runtime-tokio:使用 tokio 运行时而不启用 TLS 后端。

  • runtime-tokio-native-tls:使用 tokio 运行时和 native-tls TLS 后端(已软弃用)。

  • runtime-tokio-rustls:使用 tokio 运行时和 rustls 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 数据库引擎的支持。

  • any:添加对 Any 数据库驱动程序的支持,该驱动程序可以在运行时代理到数据库驱动程序。

  • macros:添加对 query*! 宏的支持,该宏允许编译时检查查询。

  • migrate:添加对迁移管理和 migrate! 宏的支持,该宏允许编译时嵌入迁移。

  • uuid:添加对 UUID(在 Postgres 中)的支持。

  • chrono:添加对来自 chrono 的日期和时间类型的支持。

  • time:添加对来自 time crate 的日期和时间类型的支持(作为 chrono 的替代方案,由 query! 宏首选,如果两者都启用)

  • bstr:添加对 bstr::BString 的支持。

  • bigdecimal:使用 bigdecimal crate 添加对 NUMERIC 的支持。

  • rust_decimal:使用 rust_decimal crate 添加对 NUMERIC 的支持。

  • ipnetwork:使用 ipnetwork crate 添加对 INETCIDR(在 postgres 中)的支持。

  • json:使用 serde_json crate 添加对 JSONJSONB(在 postgres 中)的支持。

  • 离线模式现在始终启用。请参阅 sqlx-cli/README.md

SQLx 不是一个 ORM!

SQLx 支持 编译时检查查询。然而,它不是通过提供 Rust API 或 DSL(领域特定语言)来构建查询来实现的。相反,它提供宏,这些宏接受常规 SQL 作为输入并确保它对您的数据库有效。这种工作的方式是 SQLx 在编译时连接到您的开发数据库,以便数据库本身验证(并返回有关您的 SQL 查询的一些信息)。这有一些潜在的意外影响

  • 由于 SQLx 从不需要解析 SQL 字符串本身,因此可以(包括数据库扩展添加的内容)使用开发数据库接受的任何语法。
  • 由于数据库允许检索查询信息的不同数量,因此从查询宏获得的 SQL 验证程度取决于数据库。

如果您正在寻找一个(异步)ORM,您可以查看基于 SQLx 构建的 ormxSeaORM

用法

请参阅 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注入。无准备查询简单,仅适用于准备语句无法使用的情况,例如各种数据库命令(例如,PRAGMASETBEGIN)。

SQLx支持使用这两种类型的所有操作。在SQLx中,一个&str被视为无准备查询,而一个QueryQueryAs结构被视为准备查询。

// 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查询终结器返回受影响的行数(如果有),并丢弃所有接收到的结果。此外,还有fetchfetch_onefetch_optionalfetch_all来接收结果。

sqlx::query返回的Query类型将从数据库返回Row<'conn>。可以通过序号或名称使用row.get()访问列值。由于Row保留对连接的不可变借用,因此一次只能存在一个Row

fetch查询终结器返回一个类似流的类型,该类型遍历结果集中的行。

// provides `try_next`
use futures::TryStreamExt;

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_onefetch_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文件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<Country>
    .await?;

// countries[0].country
// countries[0].count

为了避免在无需修改代码中访问数据库的部分时仍需要一个开发数据库来编译项目,您可以使用 sqlx 命令行工具启用“离线模式”,以缓存 SQL 查询分析的结果。请参阅 sqlx-cli/README.md

编译时验证的查询在编译时做了大量工作。将以下内容放入您的 Cargo.toml(更多信息请参阅 The Cargo Book 的 配置文件部分),使用优化构建的增量操作(如 cargo checkcargo build)可以显著加快速度。

[profile.dev.package.sqlx-macros]
opt-level = 3

1截至 2021 年 12 月,dotenv 包似乎已被废弃,所以我们现在使用 dotenvy 包。文件格式相同。

安全性

此包使用 #![forbid(unsafe_code)] 确保所有内容都在 100% 安全的 Rust 中实现。

如果启用了 sqlite 功能,则将其降级为 #![deny(unsafe_code)],并在 sqlx::sqlite 模块中使用 #![allow(unsafe_code)]。我们在与 C SQLite API 交互的几个地方。我们尝试为所假设的不变量编写每个调用的文档。我们绝对欢迎对我们的不安全代码使用进行审计和反馈。

许可证

根据您选择的以下任一项进行许可:

任您选择。

贡献

除非您明确说明,否则根据 Apache-2.0 许可证定义的任何有意提交的工作,均应按照上述方式双许可,不附加任何额外条款或条件。

依赖项

~7–28MB
~471K SLoC