sqlx-cli

从sqlx提供的数据库管理、migrations命令工具来开始学习sqlx。

安装

# 支持sqlx支持的所有数据库
$ cargo install sqlx-cli
 
# 只用于postgres
$ cargo install sqlx-cli --no-default-features --features native-tls,postgres
 
# use vendored OpenSSL (build from source)
$ cargo install sqlx-cli --features openssl-vendored
 
# use Rustls rather than OpenSSL (be sure to add the features for the databases you intend to use!)
$ cargo install sqlx-cli --no-default-features --features rustls

安装完成便可以执行sqlx命令了

$ sqlx
Command-line utility for SQLx, the Rust SQL toolkit.
 
Usage: sqlx <COMMAND>
 
Commands:
  database     Group of commands for creating and dropping your database
  prepare      Generate query metadata to support offline compile-time verification
  migrate      Group of commands for creating and running migrations
  completions  Generate shell completions for the specified shell
  help         Print this message or the help of the given subcommand(s)
 
Options:
  -h, --help     Print help
  -V, --version  Print version

配置

sqlx需要配置项就是连接的数据库,可以通过在命令行中指定--database-url选项来完成,也可以在工作目录下的.env文件夹下添加如下内容

# Postgres
DATABASE_URL=postgres://postgres@localhost/my_database

drop/create数据库

sqlx database create
sqlx database drop

管理数据库的能力这里指的就是创建和删除数据库的能力。数据库的连接地址通过前面的配置部分设置即可。

migrations

migrations是使用sqlx-cli工具的主要原因。通过migrations,可以在我们管理好数据库表的创建、升级等工作,我们只需要保证数据模式的正确即可。

添加migrations

通过sqlx migrate add <name>即可添加一个新的migration文件,格式为migrations/<timestamp>-<name>.sql。但是不推荐这样使用。

推荐的使用方式是在第一次添加migrations的时候带上-r这个选项,这样会为我们创建两个文件,一个文件用来升级、一个文件用来回滚。只需要第一次的时候加上这个选项,后面运行就不需要了

$ sqlx migrate add -r <name>
Creating migrations/20211001154420_<name>.up.sql
Creating migrations/20211001154420_<name>.down.sql

migrations执行

  • 升级sqlx migrate run
  • 回滚sqlx migrate revert 默认操作的路径都是当前目录下的migrations,如果需要修改可以在执行migrate子命令的时候指定--source选项来自定义路径,例如sqlx migrate info --source ../relative/migrations

sqlx

添加依赖

现在正是进入sqlx的使用,首先需要添加依赖,sqlx添加依赖需要开启异步运行时+TSL的feature组合。 异步运行时支持async-std, tokio, actix,TLS后端支持native-tls, rustls

[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" ] }

当然除了这些feature之外,还有数据库的feature,macro,derive这些feature,会在接下来进行使用。

创建连接池

let pool = PgPoolOptions::new()
                    .max_connections(5)
                    .connect(&url)
                    .await
                    .map_err(|e| e.to_string())?;

CRUD

sqlx不是一个orm框架,需要通过编写sql来操作数据库。

下面是操作原始sql和结果的示例

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")?;
}

但是这样比较麻烦,我们希望可以直接将查询的结果构造为对应的结构体,这需要使用的结构体上派生了FromRow

#[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);

为了进一步简化,还提供了对应宏来方便编写sql和设置参数。并且使用宏还会在编译的时候根据使用的数据库来检测sql的正确性。

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?;

下面我们就只介绍宏的写法,比较方便。还有一个就是query_as!宏,并且使用宏不需要结构体派生FromRow了。

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?;

sql的执行使用的都是query和query_as,至于最后的执行是查询还是更新就看后续调用的是fetch*簇还是execute方法了。