快速入门

在这里,会先介绍一下Diesel的一些简单应用,包括项目的初始化和简单的增删改查。这里的示例需要按照一步步操作,因为之后的操作依赖之前的操作。
这里的示例是使用的是SQLite数据库。在开始操作之前,需要安装了SQLite。但Diesel还支持其他的数据库,更多的请查看Diesel官网

关于Rust版本: 要使用DieselRust版本最少要是1.31。同时强烈建议通过rustup update stable使用Rust的最新版本。

初始化项目

首先要做的是初始化一个示例项目,可以使用IDE工具或命令行的方法来初始化项目。在这里,使用命令行初始化项目。

  1. cargo new --lib diesel_demo
  2. cd diesel_demo

接下来,需要添加Diesel依赖。还要使用一个叫.env的工具来为我们管理环境变量。具体的依赖如下:

  1. [dependencies]
  2. diesel = { version = "1.4.4", features = ["sqlite"] }
  3. dotenv = "0.15.0"

安装Diesel命令行工具

Diesel提供了一个单独的命令行工具。这个命令行工具可以提供很多的方便。为此,可以把这个工具添加到系统当中。

  1. cargo install diesel_cli

需要注意的是,在安装Diesel的命令行工具的时候,可能会遇到下面类似的错误:

  1. note: ld: library not found for -lmysqlclient
  2. clang: error: linker failed with exit code 1 (use -v to see invocation)

这表明在安装diesel命令行工具的时候,没有安装必要的客户端库(在例子中是mysqlclient)。 默认情况下:diesel命令行工具依赖下面的客户端库:

  • PostgreSQL-libpq
  • Mysql-libmysqlclient
  • SQlite-libsqlite3

当然,在默认情况下,并不需要安装所有的依赖库。可以通过指定的参数来只安装需要的依赖库。如下:

  1. cargo install diesel_cli --no-default-features --features sqlite

为项目设置Diesel

要使用diesel,需要为项目指定数据库的连接地址。为了方便和数据库的安全,需要把数据库的地址放在.env的环境变量中,并把.env文件添加到.gitignore文件中。可以通过下面的方式把数据库地址添加到.env文件中:

  1. echo DATABASE_URL=db.sqlite > .env
  2. echo .env >> .gitignore

设置好了数据库地址之后,就可以使用diesel命令行来初始化数据库了。

  1. diesel setup

在运行完了diesel setup后,diesel会创建一个数据库(如果这个数据库不存在),并且创建了一个空的迁移目录,在之后可以通过迁移目录来管理数据库模型。

此外,在运行完diesel setup后,在项目目录中还生成了一个只含有file字段的diesel.toml文件。

接下来,可以通过diesel命令行以迁移目录的行事来管理项目。先让我们使用命令创建一个迁移目录:

  1. diesel migration generate create_posts

在运行完这个命令之后,diesel命令行工具为我们创建了2个空的文件。可能会看到下面这样的输出:

  1. Creating migrations/2022-05-05-071856_create_posts/up.sql
  2. Creating migrations/2022-05-05-071856_create_posts/down.sql

迁移可以使我们随着时间的迁移来探索数据库。每一个迁移目录都包含一个up.sql文件和一个down.sql文件。
下面让我们来编辑一下up.sqldown.sql:

  1. create table posts (
  2. id integer primary key not null,
  3. title text not null,
  4. body text not null,
  5. published int not null default 0
  6. )
  1. drop table posts

使用下面的命令来应用迁移:

  1. diesel migration run

还可以使用下面的命令来确定down.sql来确保的正确性:

  1. diesel migration redo

Diesel CLI

Diesel CLI 是Diesel为我们提供的一个管理数据库模型的工具。在管理数据库模型中,Diesel CLI主要扮演了两种角色:数据库迁移和创建数据库模型对应的Rust文件
我们可以通过设置一个文件来定制Diesel CLI的具体行为。这个文件一般是在Cargo.toml文件同目录下的叫diesel.toml的文件。但我们也可以通过DIESEL_CONFIG_FILE环境变量来指定不同的文件,或者是在命令行中使用--config-file的参数来指定一个其他的配置文件。此外,我们可以通过diesel setup来自动生成一个简单的默认配置文件。
Diesel 1.3开始,diesel.toml文件包含了一个[print_schema]章节,这个章节中的所有字段都是可选的。

file字段

file字段指定了diesel把数据库模型对应的rust文件放置在项目中的什么位置。如果指定了这个字段的话,当我们运行修改数据库模型的命令(如diesel migration run)的时候,diesel会自动运行diesel print-schema命令,并且把结果输出到指定的文件中。
这意味着可以放心大胆的修改数据库模型还不用担心运行命令的时候rust对应的文件不会得到更新。强烈推荐使用此字段,以便可以使得数据库模型和对应的rust代码始终保持同步。通常情况下,这个字段的值设置为src/schema.rs
和其他的字段的不同之处在于,这个字段并不会改变diesel print-schema的行为。无论是否设置了这个字段值,print-schema都会把数据库模型输出到标准输出中。

with_docs字段

当我们把with_docs字段设置为true的时候,diesel print-schema会把数据库模型中的表和列中的注释输出。效果和在命令行中使用--with-docs参数一样。

filter字段

filter字段可以设置我们在print-schema命令中输出哪些表。这个字段提供了两种指定表的形式:包含指定的表排除指定的表。分别使用only_tablesexcept_tables来指定,两个都是接收数组为参数。示例如下:

  1. [print_schema]
  2. # 通过`only_tables` 可指定在输出中只包含 users 和 posts 表
  3. filter = { only_talbes = ["users", "posts"] }
  4. # 通过`except_tables` 可以指定在输出中排除 comments 表,其他的表都会包含在输出中
  5. filter = { except_tables = ["comments"] }

schema字段

schema字段指定了在diesel查找数据表的时候的目标数据库。效果和在命令行中指定--schema一样。这个字段目前只对PostgreSQL数据库有效。如果没有指定这个字段,默认的目标数据库是public

import_types字段

import_types字段指定了在table!宏中引入的类型声明。效果和在命令行中指定--import-types是一样的。当没有指定该字段的时候,默认引入的是diesel::sql_types声明。指定的字段如下:

  1. [print_schema]
  2. # 添加 `diesel_full_text_search` 声明
  3. import_types = ["diesel::sql_types::*", "diesel_full_text_search::types::*"]

patch_file字段

对这个字段的理解还不是很清楚,暂不解释。

常见问题

使用SQlite数据库中的问题

the trait SupportsReturningClause is not implemented for Sqlite

当我们使用类似于下面的代码的时候,就会出现上面的错误:

  1. table! {
  2. posts (id) {
  3. id -> Integer,
  4. title -> Text,
  5. body -> Text,
  6. published -> Integer
  7. }
  8. }
  9. #[derive(Queryable)]
  10. struct Post {
  11. id: i32,
  12. title: String,
  13. body: String,
  14. published: i32,
  15. }
  16. #[derive(Insertable)]
  17. #[table_name = "posts"]
  18. struct NewPost<'a> {
  19. title: &'a str,
  20. body: &'a str,
  21. }
  22. pub fn create_post<'a>(conn: &SqliteConnection,title: &'a str, body: &'a str)-> Post {
  23. let new_post = NewPost { title, body };
  24. diesel::insert_into()
  25. .values(&new_post)
  26. .get_result(conn)
  27. .expect("Error saving new post")
  28. }
  1. 这里的报错是因为`sqlite`没有实现在插入完成后返回插入内容的功能。所以,我们可以修改`create_post`方法,让这个方法返回修改的条数。修改后的`create_post`方法如下:
  1. pub fn create_post<'a>(conn: &SqliteConnection, title:&'a str, body:&'a str) -> usize {
  2. let new_post = NewPost { title, body };
  3. diesel::insert_into()
  4. .values(&new_post)
  5. .execute(conn)
  6. .expect("Error saving new post")
  7. }

required because of the requirements on the impl of Query for Paginated<table>

当我们自定义分页查询的时候,可能出现上面的错误。在这个时候,我们只要修改一个调用方式即可(在调用paginate前调用as_query())。
之前的调用方式:

  1. let result = posts
  2. .paginate(1)
  3. .per_page(2)
  4. .load_and_count_pages::<Post>(&connection)
  5. .expect("Page Error");
  1. 修正后的调用方式:
  1. let result = posts
  2. .as_query()
  3. .paginate(1)
  4. .per_page(2)
  5. .load_and_count_pages::<Post>(&connection)
  6. .expect("Page Error");

使用PostgreSQL数据库的问题

创建插入时间和更新时间字段

要在postgreSQL中使用创建时间和更新时间,可以像下面这样设置sql语句。

  1. create table users(
  2. id serial primary key not null ,
  3. name varchar not null ,
  4. hair_color varchar,
  5. create_at timestamp not null default current_timestamp,
  6. updated_at timestamp not null default current_timestamp,
  7. able bool not null default 'f'
  8. );
  9. select diesel_manage_updated_at('users');

通用问题

chrono::NaiveDateTime: diesel::Expressionis not satisfied

当我们定义一个modle的时候,可能会用到chrononaiveDateTime字段,这个时候,如果我们使用了#[derive(Serialize)这样的标记,在编译的时候就可能出现上面的错误提示。比如下面的定义:

  1. use chrono::NaiveDateTime;
  2. use serde::Serialize;
  3. #[derive(Query,Serialize)]
  4. pub struct User {
  5. pub id: u64,
  6. pub name: String,
  7. pub age: u8,
  8. pub create_at: NaiveDateTime,
  9. pub updated_at: NaiveDateTime,
  10. }
  1. 在这种情况下出现了错误的原因是,当引入`chrono`包的时候,没有引入`serde`特性导致的。只要我们在`Cargo.toml`中引入`chrono`的时候,添加`serde`特性就行。如下:
  1. chrono = { version="0.4.19", features=["serde"] }