#sql-query #database-driver #sql-database #sqlite #postgresql #sqlx #compile-time

sqlx-build-trust

🧰 Rust SQL工具包。一个异步、纯Rust的SQL crate,提供编译时检查的查询,无需DSL。支持PostgreSQL、MySQL和SQLite。

1个不稳定版本

0.7.9 2024年7月9日

979数据库接口 中排名

Download history 1029/week @ 2024-07-08 686/week @ 2024-07-15 805/week @ 2024-07-22 919/week @ 2024-07-29 995/week @ 2024-08-05 873/week @ 2024-08-12

3,648 每月下载量
9 个crate中使用 (6 直接使用)

MIT/Apache

1MB
14K SLoC

SQLx

🧰 Rust SQL工具包


The LaunchBadge团队用❤️构建

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

SQLx是一个异步、纯Rust SQL crate,提供编译时检查的查询,无需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 使用 #![forbid(unsafe_code)] 除非启用了 sqlite 功能。SQLite 驱动程序通过 libsqlite3-sys 直接调用 SQLite3 API,这需要 unsafe


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

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

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

  • 自动语句准备和缓存。当使用高级查询 API(《sqlx::query)时,每个连接都会准备和缓存语句。

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

  • 支持传输层安全性(TLS)的数据库(MySQL、MariaDB 和 PostgreSQL)。

  • 使用 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 后端(Unix上的 OpenSSL,Windows上的 SChannel,macOS上的 Secure Transport)。

  • tls-rustls:使用 rustls TLS 后端(跨平台后端,仅支持 TLS 1.2 和 1.3)。

  • postgres:添加对 PostgreSQL 数据库服务器的支持。

  • 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,您可以查看ormxSeaORM,它是建立在SQLx之上的。

用法

有关更深入的用法,请参阅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中添加以下内容,使用优化构建的增量操作(如cargo checkcargo build)可以显著加快速度(更多详细信息请参阅《Cargo Book》中的配置文件部分)。

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

1截至2021年12月dotenv包已被弃用,因此我们现在使用dotenvy包代替。文件格式相同。

安全性

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

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

许可证

根据您选择使用以下任何一个:

自由选择。

贡献

除非您明确声明,否则根据Apache-2.0许可证定义的任何贡献,均应如上所述双许可,不附加任何其他条款或条件。

依赖关系

约7–28MB
约471K SLoC