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

sqlx_wasi

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

1 个不稳定版本

0.6.2 2023 年 3 月 3 日

#2697 in 数据库接口

MIT/Apache

1.5MB
32K SLoC

SQLx

🧰 Rust SQL 工具包


LaunchBadge 团队 用 ❤️ 构建

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

SQLx 是一个异步、纯 Rust SQL 库,提供编译时检查查询,无需 DSL。

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

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

  • 数据库无关。支持 PostgreSQLMySQLSQLiteMSSQL

  • 纯 Rust。PostgreSQL 和 MySQL/MariaDB 驱动程序完全用 Rust 编写,不使用任何不安全代码。

  • 运行时无关。适用于不同的运行时(async-std / tokio / actix)和 TLS 后端(native-tlsrustls)。

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

†† SQLx 默认使用 #![forbid(unsafe_code)],除非启用 sqlite 功能。由于 SQLite 驱动与 C 进行交互,这些交互都是 unsafe


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

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

  • 行流。数据异步从数据库读取,按需解码。

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

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

  • 支持传输层安全性(TLS)(适用于 MySQLPostgreSQL)。

  • 使用 LISTENNOTIFY 进行异步通知,适用于 PostgreSQL

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

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

安装

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

# Cargo.toml
[dependencies]
# tokio + rustls
sqlx = { version = "0.6", features = [ "runtime-tokio-rustls" ] }
# async-std + native-tls
sqlx = { version = "0.6", features = [ "runtime-async-std-native-tls" ] }

运行时和 TLS 后端不是独立的特性集合,这是针对 Cargo 问题 的解决方案。

Cargo 功能标志

  • runtime-async-std-native-tls:使用 async-std 运行时和 native-tls TLS 后端。

  • runtime-async-std-rustls:使用 async-std 运行时和 rustls TLS 后端。

  • runtime-tokio-native-tls:使用 tokio 运行时和 native-tls TLS 后端。

  • runtime-tokio-rustls:使用 tokio 运行时和 rustls TLS 后端。

  • runtime-actix-native-tls:使用 actix 运行时和 native-tls TLS 后端。

  • runtime-actix-rustls:使用 actix 运行时和 rustls TLS 后端。

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

  • mysql:添加对 MySQL/MariaDB 数据库服务器的支持。

  • mssql:添加对 MSSQL 数据库服务器的支持。

  • sqlite:添加对自包含 SQLite 数据库引擎的支持。

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

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

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

  • uuid:添加了对Postgres中UUID的支持。

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

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

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

  • git2:添加了对git2::Oid的支持。

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

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

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

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

  • tls:添加了对TLS连接的支持。

  • offline:当无法访问实时数据库时(例如CI),启用离线模式构建宏。

SQLx不是一个ORM!

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

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

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

用法

有关更深入的用法,请参阅examples/文件夹。

快速入门

[dependencies]
# PICK ONE:
# Async-std:
sqlx = { version = "0.6", features = [  "runtime-async-std-native-tls", "postgres" ] }
async-std = { version = "1", features = [ "attributes" ] }

# Tokio:
sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls" , "postgres" ] }
tokio = { version = "1", features = ["full"] }

# Actix-web:
sqlx = { version = "0.6", features = [ "runtime-actix-native-tls" , "postgres" ] }
actix-web = "4"
use sqlx::postgres::PgPoolOptions;
// use sqlx::mysql::MySqlPoolOptions;
// etc.

#[async_std::main]
// or #[tokio::main]
// or #[actix_web::main]
async fn main() -> Result<(), sqlx::Error> {
    // Create a connection pool
    //  for MySQL, 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)
    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 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)],并在 #![allow(unsafe_code)]sqlx::sqlite 模块中。我们在与C SQLite API交互的几个地方都尝试记录每个调用的假设不变性。我们热烈欢迎对不安全代码使用进行审计和反馈。

许可证

在以下任一许可证下授权:

供您选择。

贡献

除非您明确声明,否则任何有意提交以包含在您的工作中的贡献,如Apache-2.0许可证中定义的,均应如上所述双授权,不附加任何额外条款或条件。

依赖项

~7–27MB
~460K SLoC