1 安装 postgresql #
$ brew install mysql-client sqlite postgresql
# 开启自动启动 postgresql
$ brew services start postgresql@14
# 确认启动成功
$ brew services list
Name Status User File
bind none
dbus none
emacs-plus@30 none
postgresql@14 started alizj ~/Library/LaunchAgents/[email protected]
unbound none
$ lsof -i tcp:5432
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
postgres 64220 alizj 7u IPv6 0x5297ba1d8b4e0e13 0t0 TCP localhost:postgresql (LISTEN)
postgres 64220 alizj 8u IPv4 0x5297ba271f55c663 0t0 TCP localhost:postgresql (LISTEN)
# 连接 postgresql,设置账号密码
$ psql -d postgres
psql (14.12 (Homebrew))
Type "help" for help.
postgres=# CREATE ROLE zj WITH LOGIN PASSWORD 'xxx';
CREATE ROLE
postgres=# ALTER ROLE zj CREATEDB;
ALTER ROLE
$ psql -l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+-------+----------+---------+-------+-------------------
postgres | alizj | UTF8 | C | C |
template0 | alizj | UTF8 | C | C | =c/alizj +
| | | | | alizj=CTc/alizj
template1 | alizj | UTF8 | C | C | =c/alizj +
| | | | | alizj=CTc/alizj
(3 rows)
2 diesel_cli #
# 编译安装 diesel_cli 前需要指定 MYSQLCLIENT_LIB_DIR 和 MYSQLCLIENT_VERSION 环境变量,否则编译失败。
$ ls -l /opt/homebrew/opt/mysql-client/lib
total 14M
-rw-r--r-- 1 alizj 6.6M 8 1 13:44 libmysqlclient.23.dylib
-r--r--r-- 1 alizj 7.1M 12 14 2023 libmysqlclient.a
lrwxr-xr-x 1 alizj 23 12 14 2023 libmysqlclient.dylib -> libmysqlclient.23.dylib
drwxr-xr-x 3 alizj 96 8 1 13:44 pkgconfig/
$ export MYSQLCLIENT_LIB_DIR=/opt/homebrew/opt/mysql-client/lib MYSQLCLIENT_VERSION=23
$ cargo install diesel_cli
# 创建环境变量文件
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ echo DATABASE_URL=postgres://zj:1234@localhost/diesel_demo > .env
# 创建 migrations 目录
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ diesel setup
Creating migrations directory at: /Users/alizj/work/code/learn-by-doing/rust/diesel_demo/migrations
Creating database: diesel_demo
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ cat diesel.toml
[print_schema]
file = "src/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
[migrations_directory]
dir = "/Users/alizj/work/code/learn-by-doing/rust/diesel_demo/migrations"
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ ls -l migrations/00000000000000_diesel_initial_setup/
total 8.0K
-rw-r--r-- 1 alizj 328 8 1 14:05 down.sql
-rw-r--r-- 1 alizj 1.2K 8 1 14:05 up.sql
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ psql -l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-------------+-------+----------+---------+-------+-------------------
diesel_demo | zj | UTF8 | C | C |
postgres | alizj | UTF8 | C | C |
template0 | alizj | UTF8 | C | C | =c/alizj +
| | | | | alizj=CTc/alizj
template1 | alizj | UTF8 | C | C | =c/alizj +
| | | | | alizj=CTc/alizj
(4 rows)
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ diesel migration generate create_posts
Creating migrations/2024-08-01-060838_create_posts/up.sql
Creating migrations/2024-08-01-060838_create_posts/down.sql
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ cat migrations/2024-08-01-060838_create_posts/up.sql
-- Your SQL goes here
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
body TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT FALSE
)
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ cat migrations/2024-08-01-060838_create_posts/down.sql
-- This file should undo anything in `up.sql`
DROP TABLE posts
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ diesel migration run
Running migration 2024-08-01-060838_create_posts
zj@a:~/work/code/learn-by-doing/rust/diesel_demo$ cat src/schema.rs # 或者 diesel print-schema
// @generated automatically by Diesel CLI.
diesel::table! {
posts (id) {
id -> Int4,
title -> Varchar,
body -> Text,
published -> Bool,
}
}
3 sql type #
diesel 将 Rust 类型转换为 diesel::sql_types 中的 diesel 类型,如 Interger, Int4, Binary, Timestamp 等, 这些是 DB 无关的抽象类型, 在 table!() 宏中使用, 后续被 DB Backend 转换为具体的 DB 字段类型。
diesel 使用 FromSql/ToSql
trait 在 diesel sql_type 类型和 Rust 类型之间转换, diesel 为绝大部分
Rust 类型以及 Uuid/Json 类型实现了这两个 trait:
- FromSql: 将 diesel sql_type 类型转换为 Rust 类型, 如 Interger -> i32;
- ToSql: 从 Rust 类型转换为 diesel sql_type 类型, 如 i32 -> Interger;
// Struct diesel::sql_types::Integer
pub struct Integer;
// 每种 DB Backend 都定义了自己的 ToSql 和 FromSql 实现,用于 sql_type 和 Rust 类型类型的转换
impl ToSql<Integer, Mysql> for i32
impl ToSql<Integer, Pg> for i32
impl ToSql<Integer, Sqlite> for i32
impl FromSql<Integer, Mysql> for i32
impl FromSql<Integer, Pg> for i32
impl FromSql<Integer, Sqlite> for i32
所以,通过 diesel sql_type 类型以及 ToSql/FromSql trait,diesel 知道如何将 Rust 类型值(如 i32)写入数据库的对应字段类型,以及如何从数据库字段值(raw bytes)转换为 Rust 类型值。
Rust 和 Diesel 类型列表:
-
SQL 文本:对应 diesel 的 Text 类型,Rust 的 String 和 &str 类型;
-
SQL interger:对应 diesel 的 Integer,Rust 的 i32 类型;
-
SQL binary:对应 diesel 的 Binary,Rust 的 Vec<u8>, &u8 类型;
-
SQL timestamp(不带 TZ):对应 diesel 的 Timestamp 和 Rust 的 chrono::NaiveDateTime;
- Mysql TIMESTAMP 类型:插入时将值从 by conn 的当前时区值转为 UTC 时区值存入 db,查询时将保存的 UTC 值转换为连接对应的时区值;
- Mysql DATIMETIME 类型:原样写入和查询,不会做任何转换;
-
SQL timestamptz(带 TZ) 对应 diesel 的 Timestamptz 和 Rust 的 chrono::DateTime;
-
SQL date 对应 diesel 的 Date 和 Rust 的 chrono::NaiveDate;
-
SQL time(不带 TZ)对应 diesel 的 Time 和 Rust 的 chrono::NaiveTime;
-
SQL interval 对应 diesel 的 Interval 和 Rust 的 PgInterval;
-
SQL boolean 对应 diesel 的 Bool 和 Rust 的 bool;
-
SQL enum 对应 diesel 的 (user-defined) 和 Rust 的 String, enum;
-
SQL uuid 对应 diesel 的 Uuid 和 Rust 的 uuid::Uuid
-
SQL json(文本) 对应 diesel 的 Json 和 Rust 的 serde_json::Value
-
SQL jsonb(二进制) 对应 diesel 的 Jsonb 和 Rust 的 serde_json::Value
-
SQL array 对应 diesel 的 Array<T> 和 Rust 的 Vec<T>, Vec<Option<T>>, &[T], &[Option<T>];
https://kotiri.com/2018/01/31/postgresql-diesel-rust-types.html
SQL TIMESTAMP 类型是不带 timezone 的类型,对应 Diesle 类型 Timestamp,对应 Rust 类型可以是 chrono::NativeDateTime.
// cargo.toml 中需要为 diesel 启用 chron feature
// [dependencies]
// diesel = { version = "2.1.0", path = "../../../diesel", features = ["mysql", "chrono"] }
// serde = { version = "1.0.130", features = ["derive"] }
// serde_json = "1.0.68"
// chrono = { version = "0.4.20", default-features = false, features = ["clock", "std"] }
// SQL:
// CREATE TABLE users (
// id INTEGER PRIMARY KEY AUTO_INCREMENT,
// name TEXT NOT NULL,
// hair_color TEXT,
// created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
// updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
// );
use chrono::NaiveDateTime;
#[cfg(test)]
use diesel::debug_query;
use diesel::insert_into;
#[cfg(test)]
use diesel::mysql::Mysql;
use diesel::prelude::*;
use serde::Deserialize;
use std::error::Error;
mod schema {
diesel::table! {
users {
id -> Integer, // diesel sql_type 类型.
name -> Text, // 默认都是 NOT NULL(对应 table 字段加了 NOT NULL)
hair_color -> Nullable<Text>, // 可插入 NULL 字段(因为对应 table 字段没有加 NOT NULL)
created_at -> Timestamp,
updated_at -> Timestamp,
}
}
}
use schema::users;
#[derive(Queryable, PartialEq, Debug)] // Queryable 要求 struct 字段必须和 table!() 生成的完全一致.
struct User {
id: i32,
name: String,
hair_color: Option<String>, // 可选字段
created_at: NaiveDateTime, // Rust 类型:chrono::NaiveDateTime
updated_at: NaiveDateTime,
}
#[derive(Deserialize, Insertable)] // Insertable 只需定义 table!() 中部分字段.
#[diesel(table_name = users)]
pub struct UserForm<'a> {
name: &'a str,
hair_color: Option<&'a str>, // 对于可插入 NULL 字段,需要使用 Option<T>, 传入 None 时插入 NULL
}
// CREATE TABLE posts (
// id INTEGER AUTO_INCREMENT PRIMARY KEY,
// title VARCHAR(255) NOT NULL,
// body TEXT NOT NULL,
// published BOOLEAN NOT NULL DEFAULT FALSE
// );
diesel::table! {
posts (id) {
id -> Integer,
title -> Varchar, // SQL VARCHAR(N) 类型对应 diesel 的 Varchar 类型
body -> Text, // SQL TEXT 类型对应 diesel 的 Text 类型
published -> Bool,
}
}
#[derive(Queryable, Selectable)]
#[diesel(table_name = posts)]
#[diesel(check_for_backend(diesel::mysql::Mysql))]
pub struct Post {
pub id: i32, // Integer 可以使用 i32 类型
pub title: String, // Varchar/Text 都可以使用 Rust String 或 &str 类型
pub body: String,
pub published: bool,
}
#[derive(Insertable)]
#[diesel(table_name = posts)]
pub struct NewPost<'a> {
pub title: &'a str,
pub body: &'a str,
}
SQL 表名惯例: 小写的复数形式, 用下划线分割: books, authors, books_authros;
对应的 Rust model struct 命名惯例: 大写的单数形式, Book, Author, BookAuthor;
表的主键: id
表的外键: 单数表名_id, 如: book_id, author_id;
4 自定义 SqlType #
SqlType trait 只包含一个 IsNull 关联类型, 一般通过 #[derive(SqlType)]
来自动实现, 它将 IsNull 关联类型设置为 Struct diesel::sql_types::is_nullable::NotNull
;
/// A marker trait for SQL types
///
/// # Deriving
///
/// This trait is automatically implemented by [`#[derive(SqlType)]`](derive@SqlType)
/// which sets `IsNull` to [`is_nullable::NotNull`]
///
pub trait SqlType: 'static {
/// Is this type nullable?
///
/// This type should always be one of the structs in the ['is_nullable`]
/// module. See the documentation of those structs for more details.
///
/// ['is_nullable`]: is_nullable
type IsNull: OneIsNullable<is_nullable::IsNullable> + OneIsNullable<is_nullable::NotNull>;
}
diesel sql_type module 预定义了一些实现 SqlType 的类型, 如 Int4/Interger/Text/Varchar 等,不同的 DB Backend 需要分别实现这些类型的转换:
/// The integer SQL type.
///
/// ### [`ToSql`](crate::serialize::ToSql) impls
///
/// - [`i32`][i32]
///
/// ### [`FromSql`](crate::deserialize::FromSql) impls
///
/// - [`i32`][i32]
///
/// [i32]: https://doc.rust-lang.org/nightly/std/primitive.i32.html
#[derive(Debug, Clone, Copy, Default, QueryId, SqlType)] // 自动实现 SqlType
#[diesel(postgres_type(oid = 23, array_oid = 1007))] // 各种 DB Backend 的字段类型
#[diesel(sqlite_type(name = "Integer"))]
#[diesel(mysql_type(name = "Long"))]
pub struct Integer;
pub type Int4 = Integer;
通过实现 SqlType/FromSql/ToSql trait,也可以自定义 diesel sql_type 类型,如 Language:
- https://github.com/diesel-rs/diesel/tree/2.2.x/examples/postgres/custom_types
- https://github.com/marvin-hansen/bazel-diesel-postgres?tab=readme-ov-file
实现细节:
- 在数据库层面定义一个自定义 SQL 类型,如 Language;
- table!() 自动给该 SQL 类型生成一个同名的 sql_types::Language 类型,作为自定义 diesel sql_type 类型,并作为 table!() 中对应字段的类型;
- 用户需要再定一个 Rust 类型,并使用 #[diesel(sql_type = crate::schema::sql_types::Language)] 将它和上面生成的 diesel sql_type 类型关联起来;
- 用户需要为上面的 Rust 类型实现 FromSql/ToSql trait,从而实现 Rust 值到数据库字段值之间的相互转换;
- 用户自定义的与 table!() 对应的 Queryable/Selectable struct model 中使用上面自定义的 Rust 类型;
// https://github.com/diesel-rs/diesel/tree/2.2.x/examples/postgres/custom_types
// up.sql
// CREATE TYPE Language AS ENUM (
// 'en', 'ru', 'de'
// );
// CREATE TABLE translations (
// word_id INTEGER NOT NULL,
// translation_id INTEGER NOT NULL,
// language Language NOT NULL,
// PRIMARY KEY (word_id, translation_id)
// )
// @generated automatically by Diesel CLI.
// 在 sql_types module 中自动生成一个名为 Language 的自定义 diesel sql_type 类型.
// 1. 使用 #[derive(diesel::sql_types::SqlType)] 来为 Language 实现 SqlType trait;
pub mod sql_types {
#[derive(diesel::sql_types::SqlType)] // 使用 derive macro 实现 SqlType
#[diesel(postgres_type(name = "language"))] // 关联的 PG 字段类型名称
pub struct Language;
}
diesel::table! {
use diesel::sql_types::*;
use super::sql_types::Language; // 导入自定义 diesel sql_type 类型
translations (word_id, translation_id) {
word_id -> Int4,
translation_id -> Int4,
language -> Language, // 使用该类型
}
}
use diesel::deserialize::{self, FromSql, FromSqlRow};
use diesel::expression::AsExpression;
use diesel::pg::{Pg, PgValue};
use diesel::serialize::{self, IsNull, Output, ToSql};
use std::io::Write;
// 再为 sql_types 类型绑定一个 Rust 类型, 需要实现 AsExpression 和 FromSqlRow
// 1. 实现 ToSql 的同时需要实现 AsExpression
// 2. 实现 FromSql 的同时需要实现 FromSqlRow
#[derive(Debug, AsExpression, FromSqlRow)]
#[diesel(sql_type = crate::schema::sql_types::Language)] // 关联 diesel sql_type 类型
pub enum Language {
En,
Ru,
De,
}
impl ToSql<crate::schema::sql_types::Language, Pg> for Language {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
match *self {
Language::En => out.write_all(b"en")?,
Language::Ru => out.write_all(b"ru")?,
Language::De => out.write_all(b"de")?,
}
Ok(IsNull::No)
}
}
impl FromSql<crate::schema::sql_types::Language, Pg> for Language {
fn from_sql(bytes: PgValue) -> deserialize::Result<Self> {
match bytes.as_bytes() {
b"en" => Ok(Language::En),
b"ru" => Ok(Language::Ru),
b"de" => Ok(Language::De),
_ => Err("Unrecognized enum variant".into()),
}
}
}
// main.rs
use self::schema::translations;
use diesel::prelude::*;
mod model;
mod schema;
#[derive(Debug, Queryable, Insertable)]
#[diesel(table_name = translations)]
pub struct Translation {
word_id: i32,
translation_id: i32,
language: model::Language, // 使用自定义类型
}
fn main() {
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let conn = &mut PgConnection::establish(&database_url)
.unwrap_or_else(|e| panic!("Error connecting to {}: {}", database_url, e));
let _ = diesel::insert_into(translations::table)
.values(&Translation {
word_id: 1,
translation_id: 1,
language: model::Language::En,
})
.execute(conn);
let t = translations::table
.select((
translations::word_id,
translations::translation_id,
translations::language,
))
.get_results::<Translation>(conn)
.expect("select");
println!("{t:?}");
}
作为简化场景,可以为自定义 Rust 类型转换为 sql_type 类型:
use diesel::deserialize::{self, FromSql};
use diesel::pg::Pg;
#[derive(FromSqlRow, AsExpression)]
#[diesel(sql_type = diesel::sql_types::Text)] // 已有的 sql_type 类型
struct Email(String);
impl FromSql<diesel::sql_types::Text, Pg> for Email {
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
let s = <String as FromSql<diesel::sql_types::Text, Pg>>::from_sql(bytes)?;
if is_valid_email(&s) {
Ok(Email(s))
} else {
Err("Invalid email format".into())
}
}
}
5 Expression/AsExpression/IntoSql #
Expression 是一个包含名为 SqlType 关联类型的 trait,而 SqlType 记录了 diesel sql_type 类型,如 Int4 及其关联的 DB Sql 字段类型。
/// Represents a typed fragment of SQL.
///
/// Apps should not need to implement this type directly, but it may be common
/// to use this in where clauses. Libraries should consider using
/// [`infix_operator!`](crate::infix_operator!) or
/// [`postfix_operator!`](crate::postfix_operator!) instead of
/// implementing this directly.
pub trait Expression {
/// The type that this expression represents in SQL
type SqlType: TypedExpressionType;
}
/// Marker trait for possible types of [`Expression::SqlType`]
///
pub trait TypedExpressionType {}
impl<ST> TypedExpressionType for ST where ST: SingleValue {}
/// A marker trait indicating that a SQL type represents a single value, as
/// opposed to a list of values.
///
/// This trait should generally be implemented for all SQL types with the
/// exception of Rust tuples. If a column could have this as its type, this
/// trait should be implemented.
///
/// # Deriving
///
/// This trait is automatically implemented by [`#[derive(SqlType)]`](derive@SqlType)
///
pub trait SingleValue: SqlType {}
table!() 为 所有 table 字段自动实现 Exression trait
:
- SqlType 关联类型为表字段对应的 diesel sql_type, 可以是内置 sql_type, 也可以是自定义 sql_type;
- sql_type 包含对应 DB
实际字段类型的信息
,如 Integer 对应 Sqlite 的 Integer 和 Mysql 的 Long;
diesel::table! {
posts (id) {
id -> Int4,
title -> Varchar,
body -> Text,
published -> Bool,
}
}
impl diesel::expression::Expression for id {
type SqlType = Int4;
}
AsExpression<T> trait 是可以将任意类型转换为 Expression<SqlType=T> 类型的 trait:
/// Converts a type to its representation for use in Diesel's query builder.
///
/// This trait is used directly. Apps should typically use [`IntoSql`] instead.
///
/// Implementations of this trait will generally do one of 3 things:
///
/// - Return `self` for types which are already parts of Diesel's query builder
/// - Perform some implicit coercion (for example, allowing [`now`] to be used as
/// both [`Timestamp`] and [`Timestamptz`].
/// - Indicate that the type has data which will be sent separately from the
/// query. This is generally referred as a "bind parameter". Types which
/// implement [`ToSql`] will generally implement `AsExpression` this way.
///
/// [`IntoSql`]: crate::IntoSql
/// [`now`]: crate::dsl::now
/// [`Timestamp`]: crate::sql_types::Timestamp
/// [`Timestamptz`]: ../pg/types/sql_types/struct.Timestamptz.html
/// [`ToSql`]: crate::serialize::ToSql
///
/// This trait could be [derived](derive@AsExpression)
pub trait AsExpression<T> where T: SqlType + TypedExpressionType,
{
/// The expression being returned
type Expression: Expression<SqlType = T>;
/// Perform the conversion
#[allow(clippy::wrong_self_convention)]
// That's public API we cannot change it to appease clippy
fn as_expression(self) -> Self::Expression;
}
diesel 为 绝大部分 Rust 基础类型
实现了 AsExpression(而没有为它们直接实现 Expression trait),对于自定义类型可以使用 #[derive(AsExpression, FromSqlRow)] 来实现 AsExpression:
- 实现自定义 TolSql 时需要同时使用 derive macro 实现 AsExpression 和 FromSqlRow;
use crate::sql_types::{
self, BigInt, Binary, Bool, Double, Float, Integer, SingleValue, SmallInt, Text,
};
#[allow(dead_code)]
mod foreign_impls {
use super::*;
use crate::deserialize::FromSqlRow;
#[derive(AsExpression, FromSqlRow)] // 自动实现 AsExpression, FromSqlRow
#[diesel(foreign_derive)]
#[diesel(sql_type = Bool)] // 对应 diesel sql_type 类型是 Bool
struct BoolProxy(bool);
#[derive(FromSqlRow)]
#[cfg_attr(feature = "mysql_backend", derive(AsExpression))]
#[diesel(foreign_derive)]
#[cfg_attr(feature = "mysql_backend", diesel(sql_type = crate::sql_types::TinyInt))]
struct I8Proxy(i8);
#[derive(AsExpression, FromSqlRow)]
#[diesel(foreign_derive)]
#[diesel(sql_type = SmallInt)]
struct I16Proxy(i16);
#[derive(AsExpression, FromSqlRow)]
#[diesel(foreign_derive)]
#[diesel(sql_type = Integer)]
struct I32Proxy(i32);
#[derive(AsExpression, FromSqlRow)]
#[diesel(foreign_derive)]
#[diesel(sql_type = BigInt)]
struct I64Proxy(i64);
#[derive(FromSqlRow)]
#[cfg_attr(
any(feature = "mysql_backend", feature = "postgres_backend"),
derive(AsExpression)
)]
#[diesel(foreign_derive)]
#[cfg_attr(
feature = "mysql_backend",
diesel(sql_type = crate::sql_types::Unsigned<crate::sql_types::TinyInt>)
)]
#[cfg_attr(feature = "postgres_backend", diesel(foreign_derive, sql_type = crate::sql_types::CChar))]
struct U8Proxy(u8);
#[derive(FromSqlRow)]
#[cfg_attr(feature = "mysql_backend", derive(AsExpression))]
#[diesel(foreign_derive)]
#[cfg_attr(feature = "mysql_backend", diesel(sql_type = crate::sql_types::Unsigned<SmallInt>))]
struct U16Proxy(u16);
#[derive(FromSqlRow)]
#[cfg_attr(
any(feature = "mysql_backend", feature = "postgres_backend"),
derive(AsExpression)
)]
#[diesel(foreign_derive)]
#[cfg_attr(feature = "mysql_backend", diesel(sql_type = crate::sql_types::Unsigned<Integer>))]
#[cfg_attr(feature = "postgres_backend", diesel(sql_type = crate::sql_types::Oid))]
struct U32Proxy(u32);
#[derive(FromSqlRow)]
#[cfg_attr(feature = "mysql_backend", derive(AsExpression))]
#[diesel(foreign_derive)]
#[cfg_attr(feature = "mysql_backend", diesel(sql_type = crate::sql_types::Unsigned<BigInt>))]
struct U64Proxy(u64);
#[derive(AsExpression, FromSqlRow)]
#[diesel(foreign_derive)]
#[diesel(sql_type = Float)]
struct F32Proxy(f32);
#[derive(AsExpression, FromSqlRow)]
#[diesel(foreign_derive)]
#[diesel(sql_type = Double)]
struct F64Proxy(f64);
#[derive(AsExpression, FromSqlRow)]
#[diesel(foreign_derive)]
#[diesel(sql_type = Text)]
#[cfg_attr(feature = "sqlite", diesel(sql_type = crate::sql_types::Date))]
#[cfg_attr(feature = "sqlite", diesel(sql_type = crate::sql_types::Time))]
#[cfg_attr(feature = "sqlite", diesel(sql_type = crate::sql_types::Timestamp))]
#[cfg_attr(feature = "postgres_backend", diesel(sql_type = crate::sql_types::Citext))]
struct StringProxy(String);
#[derive(AsExpression)]
#[diesel(foreign_derive, not_sized)]
#[diesel(sql_type = Text)]
#[cfg_attr(feature = "sqlite", diesel(sql_type = crate::sql_types::Date))]
#[cfg_attr(feature = "sqlite", diesel(sql_type = crate::sql_types::Time))]
#[cfg_attr(feature = "sqlite", diesel(sql_type = crate::sql_types::Timestamp))]
#[cfg_attr(feature = "postgres_backend", diesel(sql_type = crate::sql_types::Citext))]
struct StrProxy(str);
#[derive(FromSqlRow)]
#[diesel(foreign_derive)]
struct VecProxy<T>(Vec<T>);
#[derive(AsExpression)]
#[diesel(foreign_derive)]
#[diesel(sql_type = Binary)]
struct BinaryVecProxy(Vec<u8>);
#[derive(AsExpression)]
#[diesel(foreign_derive, not_sized)]
#[diesel(sql_type = Binary)]
struct BinarySliceProxy([u8]);
#[derive(AsExpression)]
#[diesel(foreign_derive)]
#[diesel(sql_type = Binary)]
struct BinaryArrayProxy<const N: usize>([u8; N]);
}
AsExpression 在 diesel 中主要 用作 trait 限界
,相比直接使用 Expression,AsExpression 的好处是可以=
同时传入= Rust 值类型以及对应的 diesel sql_type 类型. 例如 ExpressionMethods trait 的各方法传入的参数需要实现 AsExpression:
// Trait diesel::expression_methods::ExpressionMethods
pub trait ExpressionMethods: Expression + Sized {
// 指定的值
fn eq<T>(self, other: T) -> Eq<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
fn ne<T>(self, other: T) -> NotEq<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
fn eq_any<T>(self, values: T) -> EqAny<Self, T>
where Self::SqlType: SqlType, T: AsInExpression<Self::SqlType>
fn ne_all<T>(self, values: T) -> NeAny<Self, T>
where Self::SqlType: SqlType, T: AsInExpression<Self::SqlType>
//...
}
由于绝大部分 Rust 基础类型自动实现了 AsExpression,所以可以使用 34i32.as_expression::<Integer>()
返回一个 Expression::<Integer> 类型:
let names = users::table.select("The Amazing ".as_expression::<Text>().concat(users::name)).load(conn);
IntoSql 将自身(实现 AsExpression<U> 的 Rust 类型值)转换为指定的 diesel sql_type 类型, 如: "The Amazing ".into_sql::<Text>()
- diesel 为任意类型 T 都实现了 IntoSql trait, 但调用 IntoSql::into_sql::<U>() 方法时, Self 需要实现
AsExpression<U> + Sized
, 也就是实现 IntoSql的类型需要实现 AsExpression<U>, 即能转换为 U sql_type 类型的 Expression. - x.into_sql::<Y>() 等效于 AsExpression::<Y>::as_expression(x)
- x.as_sql::<Y>() 等效于 AsExpression::<Y>::as_expression(&x)
IntoSql::into_sql::<T>() 返回一个 T 类型的 Expression, 所以可以用在 ExpressionMthod 中:
let names = users::table.select("The Amazing ".into_sql::<Text>().concat(users::name)).load(conn);
AsExpression 和 IntoSql 都可以将 Rust 类型转换为 Expression<T> 类型,但是一般更建议使用 IntoSql。
/// Converts a type to its representation for use in Diesel's query builder.
///
/// This trait only exists to make usage of `AsExpression` more ergonomic when
/// the `SqlType` cannot be inferred. It is generally used when you need to use
/// a Rust value as the left hand side of an expression, or when you want to
/// select a constant value.
///
/// # Example
///
/// ```rust
/// # include!("../doctest_setup.rs");
/// # use schema::users;
/// #
/// # fn main() {
/// use diesel::sql_types::Text;
/// # let conn = &mut establish_connection();
/// let names = users::table
/// .select("The Amazing ".into_sql::<Text>().concat(users::name))
/// .load(conn);
/// let expected_names = vec![
/// "The Amazing Sean".to_string(),
/// "The Amazing Tess".to_string(),
/// ];
/// assert_eq!(Ok(expected_names), names);
/// # }
/// ```
pub trait IntoSql {
/// Convert `self` to an expression for Diesel's query builder.
///
/// There is no difference in behavior between `x.into_sql::<Y>()` and
/// `AsExpression::<Y>::as_expression(x)`.
fn into_sql<T>(self) -> AsExprOf<Self, T>
where
Self: AsExpression<T> + Sized,
T: SqlType + TypedExpressionType,
{
self.as_expression()
}
/// Convert `&self` to an expression for Diesel's query builder.
///
/// There is no difference in behavior between `x.as_sql::<Y>()` and
/// `AsExpression::<Y>::as_expression(&x)`.
fn as_sql<'a, T>(&'a self) -> AsExprOf<&'a Self, T>
where
&'a Self: AsExpression<T>,
T: SqlType + TypedExpressionType,
{
<&'a Self as AsExpression<T>>::as_expression(self)
}
}
// 为任意类型 T 实现 IntoSql
impl<T> IntoSql for T {}
6 disel::table! #
执行 diesel migration run
命令时,diesel cli 自动根据连接的 DB Schema 来生成 src/schema.rs 文件,该文件使用 diese::table!()
宏来定义匹配的数据库 schema:
- 缺省情况下,table!() 支持最大 32 clomuns per table,可以启用 64-column-tables/128-column-tables feature,来支持 64/128 列,但列数越多,编译越慢。
- Struct
diesel::sql_types::Nullable
用于封装可以是 NULL 的另一个类型,默认所有 table field都是 NOT NULL
。- 当在 migration up.sql 中定义的表列如果没有加 NOT NULL,则表示可以是 NULL 的,这时自动生成的 schema.rs 中的字段就是 Nullable<T>, 对应的 struct model 应该是 Option<U>;
table!() 宏的内容:
- 支持数组字段类型,如:Nullable<Array<Text>>。
- 支持 JSON 数据类型,如:Jsonb。
- 主要是字段名称和字段类型,不体现缺省值;
// table!() 宏使用的 sql type
use diesel::sql_types::*;
diesel::table! {
posts (id) { // id 为默认 PK
id -> Int4,
title -> Varchar, // 字段默认是 NOT NULL
body -> Text,
published -> Bool,
}
}
diesel::table! {
users {
id -> Integer,
name -> VarChar,
favorite_color -> Nullable<VarChar>, // 使用 Nullable 来设置可以为 NULL 的字段
}
}
diesel::table! {
users (non_standard_primary_key) { // 如果 PK 不是 id,则可以手动指定
non_standard_primary_key -> Integer,
name -> VarChar,
favorite_color -> Nullable<VarChar>,
}
}
diesel::table! {
followings (user_id, post_id) { // 组合 PK
user_id -> Integer,
post_id -> Integer,
favorited -> Bool,
}
}
// 手动为自动生成的 table 添加注释(注意是 cargo doc 注释类型,后续会保留)
diesel::table! {
/// The table containing all blog posts
posts {
/// The post's unique id
id -> Integer,
/// The post's title
title -> Text,
}
}
diesel::table! {
posts {
id -> Integer,
/// This column is named `mytype` but references the table `type` column.
#[sql_name = "type"] // 避免 Rust 关键字冲突
mytype -> Text,
}
}
diesel::table! {
api_tokens (id) {
/// The `id` column of the `api_tokens` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
id -> Int4,
/// The `token` column of the `api_tokens` table.
///
/// Its SQL type is `Bytea`.
///
/// (Automatically generated by Diesel.)
token -> Bytea,
/// The `name` column of the `api_tokens` table.
///
/// Its SQL type is `Varchar`.
///
/// (Automatically generated by Diesel.)
name -> Varchar,
/// The `created_at` column of the `api_tokens` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
created_at -> Timestamp,
/// The `last_used_at` column of the `api_tokens` table.
///
/// Its SQL type is `Nullable<Timestamp>`.
///
/// (Automatically generated by Diesel.)
last_used_at -> Nullable<Timestamp>,
/// The `revoked` column of the `api_tokens` table.
///
/// Its SQL type is `Bool`.
///
/// (Automatically generated by Diesel.)
revoked -> Bool,
/// NULL or an array of crate scope patterns (see RFC #2947)
crate_scopes -> Nullable<Array<Text>>,
/// An array of endpoint scopes or NULL for the `legacy` endpoint scope (see RFC #2947)
endpoint_scopes -> Nullable<Array<Text>>,
/// The `expired_at` column of the `api_tokens` table.
///
/// Its SQL type is `Nullable<Timestamp>`.
///
/// (Automatically generated by Diesel.)
expired_at -> Nullable<Timestamp>,
/// timestamp of when the user was informed about their token's impending expiration
expiry_notification_at -> Nullable<Timestamp>,
}
}
diesel::table! {
/// Representation of the `background_jobs` table.
///
/// (Automatically generated by Diesel.)
background_jobs (id) {
/// The `id` column of the `background_jobs` table.
///
/// Its SQL type is `Int8`.
///
/// (Automatically generated by Diesel.)
id -> Int8,
/// The `job_type` column of the `background_jobs` table.
///
/// Its SQL type is `Text`.
///
/// (Automatically generated by Diesel.)
job_type -> Text,
/// The `data` column of the `background_jobs` table.
///
/// Its SQL type is `Jsonb`.
///
/// (Automatically generated by Diesel.)
data -> Jsonb,
/// The `retries` column of the `background_jobs` table.
///
/// Its SQL type is `Int4`.
///
/// (Automatically generated by Diesel.)
retries -> Int4,
/// The `last_retry` column of the `background_jobs` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
last_retry -> Timestamp,
/// The `created_at` column of the `background_jobs` table.
///
/// Its SQL type is `Timestamp`.
///
/// (Automatically generated by Diesel.)
created_at -> Timestamp,
/// The `priority` column of the `background_jobs` table.
///
/// Its SQL type is `Int2`.
///
/// (Automatically generated by Diesel.)
priority -> Int2,
}
}
生成的代码:
- 生成一个和表同名的 module posts,同时它包含两个子 module:posts::dsl 和 posts::columns;
- posts::all_columns: 包含所有表列的 tuple,当未指定 select() 时返回的列;
- posts::dsl module,一般被 use posts::dsl::* 导入;
- 导入了 posts::columns 中定义的 id 等各种表字段的同名类型;
- 将 table 重命名为 posts;
- posts::columns module 定义了:
- pub struct star; 它实现了各种 trait;
- 每个数据表成员对应一个同名 struct 类型, 如 pub struct id; 它也实现了各种 trait;
#[allow(unused_imports,dead_code,unreachable_pub)]
pub mod posts {
pub use self::columns:: * ; // 导出各 field 字段对应的类型,如 id, title, body 等
pub mod dsl {
pub use super::columns::id;
pub use super::columns::title;
pub use super::columns::body;
pub use super::columns::published;
pub use super::table as posts; // table 别名为 posts
}
pub const all_columns:(id,title,body,published,) = (id,title,body,published,);
// 固定类型 table
pub struct table;
impl table {
pub fn star(&self) -> star {
star // star 是在 self::columns 中定义的 pub struct star;
}
}
// all_columns 对应的字段类型
pub type SqlType = (Int4,Varchar,Text,Bool,);
pub type BoxedQuery<'a,DB,ST = SqlType>
// table 实现了各种 trait
impl diesel::QuerySource for table
impl <DB>diesel::query_builder::QueryFragment<DB>for table
impl diesel::query_builder::AsQuery for table
impl diesel::Table for table
impl diesel::associations::HasTable for table
impl diesel::query_builder::IntoUpdateTarget for table
impl diesel::query_source::AppearsInFromClause<table>for table
impl <T>diesel::insertable::Insertable<T>for table
#[doc = r" Contains all of the columns of this table"]
pub mod columns {
use super::table;
// 代表 users.* 查询
pub struct star;
// star 实现了各种 trait
impl <__GB>diesel::expression::ValidGrouping<__GB>for star
impl diesel::Expression for star {
impl <DB:diesel::backend::Backend>diesel::query_builder::QueryFragment<DB>for star
impl diesel::SelectableExpression<table>for star
impl diesel::AppearsOnTable<table>for star
// 每一个 struct 成员对对应一个类型
#[allow(non_camel_case_types,dead_code)]
#[derive(Debug,Clone,Copy,diesel::query_builder::QueryId,Default)]
pub struct id;
// 每个成员类型都实现了 Expression, 在 select 中使用.
impl diesel::expression::Expression for id {
type SqlType = Int4; // 决定了 ExpressionMethods 可以使用的方法
}
impl <DB>diesel::query_builder::QueryFragment<DB>for id
impl diesel::SelectableExpression<super::table>for id
// delete/update 等的 filter() 等使用
impl <QS>diesel::AppearsOnTable<QS>for id
impl <Left,Right>diesel::SelectableExpression<diesel::internal::table_macro::Join<Left,Right,diesel::internal::table_macro::LeftOuter> , >for id
impl diesel::query_source::Column for id
// 根据 id 的类型, 自动为它实现了各种 trait
impl <T>diesel::EqAll<T>for id
impl <Rhs> ::std::ops::Add<Rhs>for id
impl <Rhs> ::std::ops::Sub<Rhs>for id
impl <Rhs> ::std::ops::Div<Rhs>for id
impl <Rhs> ::std::ops::Mul<Rhs>for id
常用方式:
- posts::table 或 posts::dsl::posts;
- posts::id/title 或 posts::dsl::id/title
一般情况下, 在函数内部导入 posts::dsl::*, 一般不使用 posts::columns::id 的形式
// 不建议:
users::table
.filter(users::name.eq("Sean"))
.filter(users::hair_color.eq("black"))
// 建议: 常用的形式:
users.filter(name.eq("Sean")).filter(hair_color.eq("black"))
// 如果要使用 star, 则可以调用 users 的 star() 方法
// 示例:
fn main() {
use self::schema::posts::dsl::posts; // 导入 posts table
// ...
let post = posts
.find(post_id)
.select(Post::as_select())
.first(connection)
.optional(); // This allows for returning an Option<Post>, otherwise it will throw an error
// ...
}
table!() 为每个 field 都实现了 Expression trait
,这些 field 都以调用 ExpressionMethod:
diesel::insert_into(animals)
.values(&vec![
(species.eq("ferret"), legs.eq(4), name.eq("Freddy")),
(species.eq("ferret"), legs.eq(4), name.eq("Jack")),
])
.execute(connection)?;
let data = animals.select((species, name))
.filter(species.eq("ferret").and(name.eq("Jack")))
.load(connection)?;
let expected = vec![
(String::from("ferret"), Some(String::from("Jack"))),
];
assert_eq!(expected, data);
7 derive macro #
参考文档: https://github.com/diesel-rs/diesel/blob/2.2.x/guide_drafts/trait_derives.md
在进行 select/update/delete 时可以传入 struct 值,但是这些 struct 类型需要按需实现如下 derive macro trait:
- AsChangeset
- Implements AsChangeset
- diesel::insert(table).on_conflict().set() 的参数类型是 AsChangeset, Eq/Grouped<Eq<Left, Right>>/Tuple 均实现了该 trait, 同时对于自定义类型 struct 实现该 trait 后, 可以传入自定义类型.
- Associations
- Implement required traits for the associations API
- 用于在关联表场景,在 child 表(含有外键的表)上定义。同时使用的还有 #[diesel(belongs_to(Book, foreign_key = book_id))] 来指定关联表和外键字段。
- Identifiable
- Implements Identifiable for references of the current type
- Identifiable 实现了 IntoUpdateTarget,所以可以将自定义 struct 作为 diesel::delete()/update() 的参数;
- Insertable
- Implements Insertable
- 为 struct 类型实现 Insertable,这样可以用于 diesel::insert_into(table).values() 的参数
- Queryable
- Implements Queryable to load the result of statically typed queries
- 从 SQL query 中创建一个 struct 类型对象;
- 可以作为
diesel::prelude::RunQueryDsl
的各方法返回值类型,如 first::<User>();
- QueryableByName
- Implements QueryableByName for untyped sql queries, such as that one generated by sql_query
- Selectable
- Implements Selectable
- 根据 struct model 和 #[diesel(table_name = XX)] 指定的 table 构造一个 select clause;
- 和 Queryable 类似, 可以作为 diesel::prelude::RunQueryDsl 的各方法返回值类型; 但是可以只指定
table 的
部分字段而非全部
; - Selectable 实现了 SelectableHelper trait, 后者提供了:
- as_returning() 方法, 可以用作 DeleteStatement/SelectStatement/UpdateStatement 的 returning() 方法的参数, 从而返回一个 struct.
- as_select() 方法, 可以用作 SelectStatement 的 select() 方法的参数, 从而返回一个 struct.
- 实现 Selectable 的对象可以组合为 tuple,它也实现了SelectableHelper,例如: users::table.inner_join(posts::table).select(<(User, PostTitle)>::as_select()).first(connection)?;
一般需要根据自动生成的 table!() 的表定义创建如下几个 struct 类型:
- 和 table!() schema 严格一一匹配的 Queryable, Selectable 的 struct;
- insert() 时指定要插入的部分字段 struct,需要实现 Insertable,未列出的字段插入缺省值;
- update 的 set() 时传入的 struct, 需要实现 Identifiable ,只会 update 这个 struct 中列出的字段;
Struct 字段类型:
- struct 默认都是 NOT NULL 字段;
- 如果数据库字段有 DEFAULT 值,则需要使用 Option<T> 类型,当传入 None 时使用 T 的缺省值;
- 如果数据库字段有 DEFAULT 值,则是可以为 NULL 的,需要使用 Option<Option<T>> 类型;
use diesel::prelude::*;
#[derive(Queryable, Selectable)]
// table_name 是 Selectable 所需要的,默认为 struct 全小写名称加 s。
// check_for_backend 用于编译时静态检查,即检查 struct 成员类型和数据库表定义是否完全一致。
#[diesel(table_name = crate::schema::posts)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Post {
pub id: i32, // 默认表的 PK 是 id 字段。
pub title: String,
pub body: String,
pub published: bool,
}
// 用于 diesel::insert_into(table).values() 的参数,需要实现 Insertable
// 如果使用 &str, 则需要为 struct 指定 lifetime
#[derive(Insertable)]
#[diesel(table_name = posts)]
pub struct NewPost<'a> {
pub title: &'a str,
pub body: &'a str,
}
let new_post = NewPost { title, body };
diesel::insert_into(posts::table)
.values(&new_post)
.returning(Post::as_returning()) // Selectable 可以作为 returning() 的参数
.get_result(conn)
.expect("Error saving new post");
let post = posts
.find(post_id)
.select(Post::as_select()) // Selectable 可以作为 select() 的参数
.first(connection)
.optional(); // This allows for returning an Option<Post>, otherwise it will throw an error
// 对于有缺省值的列,对应的数据库 schema 定义类似于 ~NOT NULL DEFAULT 'Green'~.
#[derive(Insertable)]
#[diesel(table_name = brands)]
struct NewBrand {
color: Option<String>, // Option<T> 表示该字段是可选的,在传入 None 时使用缺省值。
}
let new_brand = NewBrand { color: Some("Red".into()) };
diesel::insert_into(brands)
.values(&new_brand)
.execute(connection)
.unwrap();
// Insert the default color
let new_brand = NewBrand { color: None }; // color 插入缺省值
diesel::insert_into(brands)
.values(&new_brand)
.execute(connection)
.unwrap();
// 对于 Option<T> 类型的字段,如果在 insert 时值为 None,则 diesel 插入该字段的缺省值。
#[derive(Insertable)]
#[diesel(table_name = brands)]
struct NewBrand {
color: Option<String>,
}
use schema::{users, posts};
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct User {
id: i32,
name: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct Post {
id: i32,
user_id: i32,
title: String,
}
let (first_user, first_post) = users::table
.inner_join(posts::table)
.select(<(User, Post)>::as_select()) // tuple 也实现了 Selectable
.first(connection)?;
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct User {
id: i32,
name: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
#[diesel(table_name = posts)]
struct PostTitle {
title: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct UserPost {
#[diesel(embed)]
user: User, // 支持嵌入字段
#[diesel(embed)]
post_title: PostTitle,
}
let first_user_post = users::table
.inner_join(posts::table)
.select(UserPost::as_select())
.first(connection)?;
let expected_user_post = UserPost {
user: User {
id: 1,
name: "Sean".into(),
},
post_title: PostTitle {
title: "My first post".into(),
},
};
assert_eq!(expected_user_post, first_user_post);
// 示例: Identifiable 可以作为 update() 的参数, AsChangeset 可以作为 set() 的参数. Identifiable
// 需要有作为 PK 的 id 字段.
#[derive(Queryable, Identifiable, AsChangeset)]
#[diesel(table_name = posts)]
pub struct Post {
pub id: i64,
pub title: String,
pub body: String,
pub draft: bool,
pub publish_at: SystemTime,
pub visit_count: i32,
}
diesel::update(post) // post 是上面的实现了 Identifiable 的 Post 类型值, 必须要设置 id 字段.
.set(posts::draft.eq(false))
.execute(conn)
update(post)
// 等效于
update(posts.find(post.id))
// 或
update(posts.filter(id.eq(post.id))).
// Associations 关联表
#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq)]
#[diesel(table_name = books)]
pub struct Book {
pub id: i32,
pub title: String,
}
#[derive(Queryable, Selectable, Identifiable, Associations, Debug, PartialEq)]
#[diesel(belongs_to(Book, foreign_key = book_id))] // foreign_key 默认为 {table}_id 字段
#[diesel(table_name = pages)]
pub struct Page {
pub id: i32,
pub page_number: i32,
pub content: String,
pub book_id: i32,
}
SQL 表名惯例: 小写的复数形式, 用下划线分割: books, authors, books_authros;
对应的 Rust model struct 命名惯例: 大写的单数形式, Book, Author, BookAuthor;
表的主键: id
表的外键: 单数表名_id, 如: book_id, author_id;
8 QueryDsl #
table!() 宏为 table 实现了 QueryDsl, 故可以在 table 对象
上使用 QueryDsl 提供的各种方法(Query
builder methods)来查询数据。
// Trait diesel::query_dsl::QueryDsl
pub trait QueryDsl: Sized {
// 唯一性
fn distinct(self) -> Distinct<Self> where Self: DistinctDsl
fn distinct_on<Expr>(self, expr: Expr) -> DistinctOn<Self, Expr> where Self: DistinctOnDsl<Expr>
// 指定返回的字段
fn select<Selection>(self, selection: Selection) -> Select<Self, Selection>
where Selection: Expression, Self: SelectDsl<Selection> { ... }
// 计数
fn count(self) -> Select<Self, CountStar> where Self: SelectDsl<CountStar>
// join 查询
fn inner_join<Rhs>(self, rhs: Rhs) -> InnerJoin<Self, Rhs>
fn left_outer_join<Rhs>(self, rhs: Rhs) -> LeftJoin<Self, Rhs>
fn left_join<Rhs>(self, rhs: Rhs) -> LeftJoin<Self, Rhs>
// 过滤
fn filter<Predicate>(self, predicate: Predicate) -> Filter<Self, Predicate>
where Self: FilterDsl<Predicate>
fn or_filter<Predicate>( self, predicate: Predicate, ) -> OrFilter<Self, Predicate>
where Self: OrFilterDsl<Predicate>
// 使用主键值查找一条记录
fn find<PK>(self, id: PK) -> Find<Self, PK> where Self: FindDsl<PK>
// 排序字段
fn order<Expr>(self, expr: Expr) -> Order<Self, Expr> where Expr: Expression, Self: OrderDsl<Expr>
fn order_by<Expr>(self, expr: Expr) -> OrderBy<Self, Expr> where Expr: Expression, Self: OrderDsl<Expr>
fn then_order_by<Order>(self, order: Order) -> ThenOrderBy<Self, Order> where Self: ThenOrderDsl<Order>
// 限制返回条数
fn limit(self, limit: i64) -> Limit<Self> where Self: LimitDsl
fn offset(self, offset: i64) -> Offset<Self> where Self: OffsetDsl
// 聚合
fn group_by<GB>(self, group_by: GB) -> GroupBy<Self, GB> where GB: Expression, Self: GroupByDsl<GB>
// 聚合过滤
fn having<Predicate>(self, predicate: Predicate) -> Having<Self, Predicate> where Self: HavingDsl<Predicate>
fn for_update(self) -> ForUpdate<Self> where Self: LockingDsl<ForUpdate>
fn for_no_key_update(self) -> ForNoKeyUpdate<Self> where Self: LockingDsl<ForNoKeyUpdate>
fn for_share(self) -> ForShare<Self> where Self: LockingDsl<ForShare>
fn for_key_share(self) -> ForKeyShare<Self> where Self: LockingDsl<ForKeyShare>
fn skip_locked(self) -> SkipLocked<Self> where Self: ModifyLockDsl<SkipLocked>
fn no_wait(self) -> NoWait<Self> where Self: ModifyLockDsl<NoWait>
fn into_boxed<'a, DB>(self) -> IntoBoxed<'a, Self, DB> where DB: Backend, Self: BoxedDsl<'a, DB>
fn single_value(self) -> SingleValue<Self> where Self: SingleValueDsl
fn nullable(self) -> NullableSelect<Self> where Self: SelectNullableDsl
}
// Table 和 SelectStatement 都实现了 QueryDsl
impl<'a, ST, QS, DB, GB> QueryDsl for BoxedSelectStatement<'a, ST, QS, DB, GB>
impl<F, S, D, W, O, LOf, G, H, LC> QueryDsl for SelectStatement<F, S, D, W, O, LOf, G, H, LC>
impl<S: AliasSource> QueryDsl for Alias<S>
impl<T: Table> QueryDsl for T // Table 实现了 QueryDsl
分析 SelectDSL: 最终返回的是 SelectStatement
// 分析 select():
pub trait QueryDsl: Sized {
// ...
fn select<Selection>(self, selection: Selection) -> Select<Self, Selection>
where Selection: Expression, Self: SelectDsl<Selection> { ... }
// ..
}
// Select 是 SelectDsl 的 Output 类型
pub type Select<Source, Selection> = <Source as SelectDsl<Selection>>::Output;
pub trait SelectDsl<Selection: Expression> {
type Output;
// Required method
fn select(self, selection: Selection) -> Self::Output;
}
// Table 实现了 SelectDsl
// https://docs.diesel.rs/2.2.x/src/diesel/query_dsl/select_dsl.rs.html#22-33
impl<T, Selection> SelectDsl<Selection> for T
where
Selection: Expression,
T: Table,
T::Query: SelectDsl<Selection>,
{
type Output = <T::Query as SelectDsl<Selection>>::Output;
fn select(self, selection: Selection) -> Self::Output {
self.as_query().select(selection)
}
}
// table!() 宏展开后,为 table 实现 AsQuery trait,
// as_query() 返回的 Self::Query 类型是 SelectStatement
impl diesel::query_builder::AsQuery for table {
type SqlType = SqlType;
type Query = diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<Self>> ;
fn as_query(self) -> Self::Query {
diesel::internal::table_macro::SelectStatement::simple(self)
}
}
分析 distinct(), 最终返回的还是 SelectStatement:
pub trait QueryDsl: Sized {
fn distinct(self) -> Distinct<Self> where Self: DistinctDsl
// ...
}
pub type Distinct<Source> = <Source as DistinctDsl>::Output;
pub trait DistinctDsl {
/// The type returned by `.distinct`
type Output;
/// See the trait documentation.
fn distinct(self) -> dsl::Distinct<Self>;
}
// https://docs.diesel.rs/2.2.x/src/diesel/query_dsl/distinct_dsl.rs.html#26
impl<T> DistinctDsl for T
where
T: Table + AsQuery<Query = SelectStatement<FromClause<T>>>,
T::DefaultSelection: Expression<SqlType = T::SqlType> + ValidGrouping<()>,
T::SqlType: TypedExpressionType,
{
type Output = dsl::Distinct<SelectStatement<FromClause<T>>>;
fn distinct(self) -> dsl::Distinct<SelectStatement<FromClause<T>>> {
self.as_query().distinct()
}
}
其它 QueryDsl 方法返回值类型也都类似,最终 都是
SelectStatement
类型,所以:
- 这些 QueryDsl 的方法可以链式调用;
- 由于
SelectStatement
又实现了 Expression,所以方法调用结果也可以作为 QueryDsl 方法的参数。
SelectStatement 还实现了各种 diesel::query_dsl::methods module 提供的各种 xxDSL trait,每个 diesel::query_dsl::methods::xxDSL trait 都提供了一种方法,如 DistinctDsl 提供了 distinct 方法, 它们的结果还是 SelectStatement 类型, 故可以链式调用:
- BoxedDsl
- The into_boxed method
- DistinctDsl
- The distinct method
- DistinctOnDsl
- The distinct_on method
- ExecuteDsl
- The execute method
- FilterDsl
- The filter method
- FindDsl
- The find method
- GroupByDsl
- The group_by method
- HavingDsl
- The having method
- LimitDsl
- The limit method
- LoadQuery
- The load method
- LockingDsl
- Methods related to locking select statements
- ModifyLockDsl
- Methods related to modifiers on locking select statements
- OffsetDsl
- The offset method
- OrFilterDsl
- The or_filter method
- OrderDsl
- The order method
- SelectDsl
- The
select
method, 指定要返回的字段 tuple 或实现 Selectable 的 struct - SelectNullableDsl
- The nullable method
- SingleValueDsl
- The single_value method
- ThenOrderDsl
- The then_order_by method
let complex_query = users
.filter(name.like("%Doe%"))
.filter(age.between(18, 30))
.order(age.desc())
.limit(5)
.load<User>(&connection)?;
let data = users::table
.inner_join(posts::table)
.select((users::name, posts::title))
.filter(posts::published.eq(true))
.load<(&str, &str)>(&connection)?;
9 expression methods #
各种 QueryDsl 方法的参数主要是 Expression
, 而 table!()
宏为各种 table field 类型实现了 Expression。
// Trait diesel::expression_methods::BoolExpressionMethods
pub trait BoolExpressionMethods: Expression + Sized {
// Provided methods
fn and<T, ST>(self, other: T) -> And<Self, T, ST>
where Self::SqlType: SqlType,
ST: SqlType + TypedExpressionType,
T: AsExpression<ST>,
And<Self, T::Expression>: Expression { ... }
fn or<T, ST>(self, other: T) -> Or<Self, T, ST>
where Self::SqlType: SqlType,
ST: SqlType + TypedExpressionType,
T: AsExpression<ST>,
Or<Self, T::Expression>: Expression { ... }
}
// Trait diesel::expression_methods::TextExpressionMethods
pub trait TextExpressionMethods: Expression + Sized {
// Provided methods
fn concat<T>(self, other: T) -> Concat<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
// This method is case insensitive for SQLite and MySQL. On PostgreSQL, LIKE is case
// sensitive. You may use ilike() for case insensitive comparison on PostgreSQL.
fn like<T>(self, other: T) -> Like<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
fn not_like<T>(self, other: T) -> NotLike<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
}
// Trait diesel::expression_methods::ExpressionMethods
pub trait ExpressionMethods: Expression + Sized {
// 指定的值
fn eq<T>(self, other: T) -> Eq<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
fn ne<T>(self, other: T) -> NotEq<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
fn eq_any<T>(self, values: T) -> EqAny<Self, T>
where Self::SqlType: SqlType, T: AsInExpression<Self::SqlType>
fn ne_all<T>(self, values: T) -> NeAny<Self, T>
where Self::SqlType: SqlType, T: AsInExpression<Self::SqlType>
// 是否为 null
fn is_null(self) -> IsNull<Self>
fn is_not_null(self) -> IsNotNull<Self>
// 大小
fn gt<T>(self, other: T) -> Gt<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
fn ge<T>(self, other: T) -> GtEq<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
fn lt<T>(self, other: T) -> Lt<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
fn le<T>(self, other: T) -> LtEq<Self, T>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>
// 范围
fn between<T, U>(self, lower: T, upper: U) -> Between<Self, T, U>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>, U: AsExpression<Self::SqlType>
fn not_between<T, U>(self, lower: T, upper: U) -> NotBetween<Self, T, U>
where Self::SqlType: SqlType, T: AsExpression<Self::SqlType>, U: AsExpression<Self::SqlType>
// 排序
fn desc(self) -> Desc<Self>
fn asc(self) -> Asc<Self>
}
diesel 为 Expression 实现了 diesel::expression_methods::ExpressionMethods
, 这样 Expression 对象可以调用 ExpressionMethods 中的各种方法,方法的结果类型,如 Eq/Grouped 等 又实现了 Expression/QueryFragment
,所以可以作为各种 QueryDsl 方法的输入:
// Expression 实现了 ExpressionMethods
impl<T> ExpressionMethods for T where T: Expression, T::SqlType: SingleValue
// 以 eq() 返回的 Eq 为例, 它是 Grouped 类型, 而 Grouped 实现了 Expression trait
pub type Eq<Lhs, Rhs> = Grouped<Eq<Lhs, AsExpr<Rhs, Lhs>>>;
// https://docs.diesel.rs/2.2.x/src/diesel/expression/grouped.rs.html
#[derive(Debug, Copy, Clone, QueryId, Default, DieselNumericOps, ValidGrouping)]
pub struct Grouped<T>(pub T);
// Grouped 也实现了 Expression
impl<T: Expression> Expression for Grouped<T> {
type SqlType = T::SqlType;
}
impl<T, DB> QueryFragment<DB> for Grouped<T>
where T: QueryFragment<DB>, DB: Backend + DieselReserveSpecialization,
{
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
out.push_sql("(");
self.0.walk_ast(out.reborrow())?;
out.push_sql(")");
Ok(())
}
}
总结:
- QueryDsl 和各种 XXDsl 方法都返回
SelectStatement
, 所以可以链式调用; - 链式调用的方法输入是 Expression, 且 Expression 实现了 ExpressionMethods, 所以可以对 Expresion 调用 ExpressionMethods 的各方法;
- ExpressionMethods 各方法的结果类型
又实现了
Expression, 所以可以作为各种 xxDSL 方法的参数.
// name 是 Expression,name.eq("Sean") 结果类型 Eq 也是 Expression, 所以可以作为 filter() 的参数
let seans_id = users.filter(name.eq("Sean")).select(id).first(connection);
// species.eq("ferret") 是 Expression,.and() 结果还是 Expression, 所以可以作为 filter() 的参数
let data = animals.select((species, name))
.filter(species.eq("ferret").and(name.eq("Jack")))
.load(connection)?;
let expected = vec![ (String::from("ferret"), Some(String::from("Jack"))), ];
assert_eq!(expected, data);
let mut query = posts::table
.order(posts::published_at.desc()) // 按指定字段排序
.filter(posts::published_at.is_not_null()) // 非 NULL 的记录
.inner_join(users::table)
.select((posts::all_columns, (users::id, users::username)))
.paginate(page);
let data = users.select(id).filter(name.eq("Sean"));
assert_eq!(Ok(1), data.first(connection));
10 select/returning() #
SelectStatement 的 select() 方法(由 SelectDsl 提供)指定返回值类型(未指定时返回 table 所有字段):
- 各 table field 类型以及它们的 tuple 类型都实现了该 trait;
- 通过 Selectable derive macro 定义的 struct,它的 as_select() 方法返回值也实现了该 trait
tuple 类型:
let mut query = posts::table
.order(posts::published_at.desc())
.filter(posts::published_at.is_not_null())
.inner_join(users::table)
// posts::all_columns 是 table!() 自动生成的包含所有列的 tuple
.select((posts::all_columns, (users::id, users::username)))
.paginate(page);
// tuple 类型可以指定自动转换为 Struct 类型
#[derive(Queryable, Identifiable, Debug, PartialEq, Eq)]
pub struct User {
pub id: i32,
pub username: String,
}
fn register_user(conn: &mut PgConnection, username: &str, password: &str, ) -> Result<User, AuthenticationError> {
let salt = SaltString::from_b64(SALT_STRING)?;
let argon2 = Argon2::default();
let hashed_password = argon2
.hash_password(password.as_bytes(), &salt)?
.to_string();
insert_into(users::table)
.values((
users::username.eq(username),
users::hashed_password.eq(hashed_password),
))
.returning((users::id, users::username)) // 返回一个 tuple
.get_result(conn) // 返回 (i32, String) 类型,被自动转换为 User 类型
.map_err(AuthenticationError::DatabaseError)
}
// https://github.com/diesel-rs/diesel/blob/2.2.x/examples/postgres/advanced-blog-cli/src/auth.rs
#[derive(Queryable, Identifiable, Debug, PartialEq, Eq)]
pub struct User {
pub id: i32,
pub username: String,
}
#[derive(Queryable)]
pub struct UserWithPassword {
user: User, // 嵌入式字段
password: String,
}
fn find_user(conn: &mut PgConnection, username: &str, password: &str,) -> Result<Option<User>, AuthenticationError> {
let user_and_password = users::table
.filter(users::username.eq(username))
// 返回一个 tuple,其中第一个元素为 tuple
// 展开后与 UserWithPassword 一一对应,所以可以自动转换为 UserWithPassword
.select(((users::id, users::username), users::hashed_password))
.first::<UserWithPassword>(conn)
.optional()
.map_err(AuthenticationError::DatabaseError)?;
if let Some(user_and_password) = user_and_password {
let parsed_hash = PasswordHash::new(&user_and_password.password)?;
Argon2::default()
.verify_password(password.as_bytes(), &parsed_hash)
.map_err(|e| match e {
argon2::password_hash::Error::Password => IncorrectPassword,
_ => AuthenticationError::Argon2Error(e),
})?;
Ok(Some(user_and_password.user))
} else {
Ok(None)
}
}
Selectable
derive macro 实现了 SelectableHelper trait, 后者提供了:
- as_returning() 方法, 可以用作 DeleteStatement/SelectStatement/UpdateStatement 的 retuning() 方法的参数, 从而返回一个 struct.
- as_select() 方法, 可以用作 SelectStatement 的
select()
方法(由SelectDsl
定义)的参数, 从而返回一个 struct.
#[derive(Queryable, Selectable)]
// table_name 是 Selectable 所需要的,默认为 struct 全小写名称加 s。
// check_for_backend 用于编译时静态检查,即检查 struct 成员类型和数据库表定义是否完全一致。
#[diesel(table_name = crate::schema::posts)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Post {
pub id: i32, // 默认表的 PK 是 id 字段。
pub title: String,
pub body: String,
pub published: bool,
}
let new_post = NewPost { title, body };
diesel::insert_into(posts::table)
.values(&new_post)
.returning(Post::as_returning()) // Selectable 可以作为 returning() 的参数
.get_result(conn)
.expect("Error saving new post");
let post = posts
.find(post_id)
.select(Post::as_select()) // Selectable 可以作为 select() 的参数
.first(connection)
.optional(); // This allows for returning an Option<Post>, otherwise it will throw an error
let data = users::table
.inner_join(posts::table)
.select((users::name, posts::title))
.filter(posts::published.eq(true))
.load<(&str, &str)>(&connection)?; // RunQueryDsl 的返回值类型需要与 select() 的参数类型匹配
如果 struct A 和 struct B 都实现了 Selectable,则它们的 tuple 类型如 (A, B) 也实现了 SelectableHelper,可以调用它们的 as_select() 方法的返回值作为 select() 的参数:
use schema::{users, posts};
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct User {
id: i32,
name: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct Post {
id: i32,
user_id: i32,
title: String,
}
let (first_user, first_post) = users::table
.inner_join(posts::table)
.select(<(User, Post)>::as_select())
.first(connection)?;
let expected_user = User { id: 1, name: "Sean".into() };
assert_eq!(expected_user, first_user);
let expected_post = Post { id: 1, user_id: 1, title: "My first post".into() };
assert_eq!(expected_post, first_post);
Selectable 的 struct 可以只 匹配部分
table field,不像 Queryable 那样需要严格完整匹配:
use schema::{users, posts};
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct User {
id: i32,
name: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
#[diesel(table_name = posts)]
struct PostTitle {
title: String, // 部分字段
}
let (first_user, first_post_title) = users::table
.inner_join(posts::table)
.select(<(User, PostTitle)>::as_select())
.first(connection)?;
let expected_user = User { id: 1, name: "Sean".into() };
assert_eq!(expected_user, first_user);
let expected_post_title = PostTitle { title: "My first post".into() };
assert_eq!(expected_post_title, first_post_title);
除了 tuple 组合 Selectable 外,还可以使用 embed 来嵌入 struct:
use schema::{users, posts};
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct User {
id: i32,
name: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
#[diesel(table_name = posts)]
struct PostTitle {
title: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct UserPost {
#[diesel(embed)]
user: User,
#[diesel(embed)]
post_title: PostTitle,
}
let first_user_post = users::table
.inner_join(posts::table)
.select(UserPost::as_select())
.first(connection)?;
let expected_user_post = UserPost {
user: User {
id: 1,
name: "Sean".into(),
},
post_title: PostTitle {
title: "My first post".into(),
},
};
assert_eq!(expected_user_post, first_user_post);
通过使用 select_expression,select_expression_type 和 #[dsl::auto_type] 宏,可以为自定义 struct 灵活指定查询规则:
use schema::{users, posts};
use diesel::dsl;
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct User {
id: i32,
name: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
#[diesel(table_name = posts)]
struct PostTitle {
title: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct UserPost {
#[diesel(select_expression = users::columns::id)]
#[diesel(select_expression_type = users::columns::id)]
id: i32,
#[diesel(select_expression = users::columns::name)]
name: String,
#[diesel(select_expression = complex_fragment_for_title())]
title: String,
#[diesel(select_expression = diesel::dsl::now)]
access_time: chrono::NaiveDateTime,
#[diesel(select_expression = users::columns::id.eq({let id: i32 = FOO; id}))]
user_id_is_foo: bool,
}
const FOO: i32 = 42; // Type of FOO can't be inferred automatically in the select_expression
#[dsl::auto_type]
fn complex_fragment_for_title() -> _ {
// See the `#[dsl::auto_type]` documentation for examples of more complex usage
posts::columns::title
}
let first_user_post = users::table
.inner_join(posts::table)
.select(UserPost::as_select())
.first(connection)?;
let expected_user_post = UserPost {
id: 1,
name: "Sean".into(),
title: "My first post".into(),
access_time: first_user_post.access_time,
user_id_is_foo: false,
};
assert_eq!(expected_user_post, first_user_post);
select() 是 SelectDsl trait 提供的方法,SelectStatement 实现了它。但是 DeleteStatement/UpdateStatement 并没有实现 SelectDsl,对于这两种语句,需要使用 returning() 方法来指定返回值类型。
DeleteStatement/SelectStatement/UpdateStatement 的 returning() 方法的参数是 SelectableExpression:
- 各 table field 类型以及它们的 tuple 类型都实现了该 trait;
- 通过 Selectable derive macro 定义的 struct,它的 as_returning() 方法返回值也实现了该 trait
对于支持 RETURNING clause
的数据库, 如 PostgreSQL 和 SQLite, 可以调用 .get_results() 来获取插入的记录,没有调用 returning() 方法时返回所有列:
let inserted_users = insert_into(users)
.values(&vec![
(id.eq(1), name.eq("Sean")),
(id.eq(2), name.eq("Tess")),
])
.get_results(conn)?;
// 等效于:
// INSERT INTO "users" ("id", "name") VALUES ($1, $2), ($3, $4)
// RETURNING "users"."id", "users"."name", "users"."hair_color",
// "users"."created_at", "users"."updated_at"
// -- binds: [1, "Sean", 2, "Tess"]
let expected_users = vec![
User {
id: 1,
name: "Sean".into(),
hair_color: None,
created_at: now,
updated_at: now,
},
User {
id: 2,
name: "Tess".into(),
hair_color: None,
created_at: now,
updated_at: now,
},
];
assert_eq!(expected_users, inserted_users);
get_result/get_results() 默认然会所有列, 使用 returning() 来指定要返回的列:
use schema::users::dsl::*;
insert_into(users)
.values(name.eq("Ruby"))
.returning(id)
.get_result(conn)
// 等效于:
// INSERT INTO "users" ("name") VALUES ($1)
// RETURNING "users"."id"
// -- binds: ["Ruby"]
// returning 表字段 tuple
let inserted_user = insert_into(users)
.values(new_users)
.returning((name, hair_color))
.get_result::<(String, Option<String>)>(connection)
.unwrap();
let expected_user = ("Sean".to_string(), Some("Black".to_string()));
// returning Selectable struct
let deleted_name = diesel::delete(users.filter(name.eq("Sean")))
// users table 的 name 字段类型实现了 SelectableExpression,如果要返回多个字段,则需要使用
// tuple 类型。然后在 get_result::<(Type1, Type2)>(connection) 中指定返回值类型。
.returning(name)
.get_result(connection);
assert_eq!(Ok("Sean".to_string()), deleted_name);
11 RunQueryDsl #
Trait diesel::prelude::RunQueryDsl
根据传入的 Connection 执行实际的 SQL 操作:
- execute/load() 返回实际影响的
计数条数 usize
; - get_result/get_results()/first() 返回插入/更新后的值类型 <U>, 如果没有调用 returning() 则返回表所有字段.
- get_result() 返回 0 个记录表示出错, 如果要返回 0 或 1 个记录需要使用get_result(…).optional();
- first() 返回一个结果记录;
pub trait RunQueryDsl<Conn>: Sized {
// 返回受影响的 行数量
fn execute(self, conn: &mut Conn) -> QueryResult<usize>
where Conn: Connection, Self: ExecuteDsl<Conn>
// 返回多行 Vec
fn load<'query, U>(self, conn: &mut Conn) -> QueryResult<Vec<U>>
where Self: LoadQuery<'query, Conn, U>
// 返回迭代器
fn load_iter<'conn, 'query: 'conn, U, B>(
self, conn: &'conn mut Conn, ) -> QueryResult<Self::RowIter<'conn>>
where U: 'conn, Self: LoadQuery<'query, Conn, U, B> + 'conn
// 返回单行记录
fn get_result<'query, U>(self, conn: &mut Conn) -> QueryResult<U>
where Self: LoadQuery<'query, Conn, U>
// 返回多行 Vec
fn get_results<'query, U>(self, conn: &mut Conn) -> QueryResult<Vec<U>>
where Self: LoadQuery<'query, Conn, U>
// 返回单行记录
fn first<'query, U>(self, conn: &mut Conn) -> QueryResult<U>
where Self: LimitDsl, Limit<Self>: LoadQuery<'query, Conn, U> { ... }
}
SelectStatement/SqlQuery/Alias/SqlLiteral/Table/DeleteStatement/InsertStatement/UpdateStatement 均实现了 RunQueryDsl, 用于执行实际的 SQL 操作。
RunQueryDsl 的 get_result/get_results/first() 方法返回的结果可以保存到实现了 Selectable 或 Queryable trait
的自定义类型, 或者 tuple 中:
- diesel 为 tuple 也实现了 Queryable,故可以作为方法的返回值类型;
#[derive(Queryable, PartialEq, Debug)]
struct User {
id: i32,
name: String,
}
let first_user: User = users.order_by(id).first(connection)?;
#[derive(Identifiable, Queryable, Selectable, Clone, Eq, Hash, PartialEq, Serialize, Deserialize, Debug)]
// 编译时检查,确保 struct 成员和数据库中 table 定义顺序一致
#[diesel(table_name = organization)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Organization {
pub id: Uuid,
pub name: String,
pub created_at: i64,
pub updated_at: Option<i64>,
pub products: Vec<Product>,
}
use schema::{users, posts};
#[derive(Debug, PartialEq, Queryable, Selectable)]
struct User {
id: i32,
name: String,
}
#[derive(Debug, PartialEq, Queryable, Selectable)]
#[diesel(table_name = posts)]
struct PostTitle {
title: String,
}
let (first_user, first_post_title) = users::table
.inner_join(posts::table)
.select(<(User, PostTitle)>::as_select())
.first(connection)?;
let expected_user = User { id: 1, name: "Sean".into() };
assert_eq!(expected_user, first_user);
let expected_post_title = PostTitle { title: "My first post".into() };
assert_eq!(expected_post_title, first_post_title);
// load 方法返回一系列值
let inserted_posts = posts::table
.select(posts::title)
.load::<String>(conn)?;
let expected = vec!["Sean's First Post", "Tess's First Post"];
assert_eq!(expected, inserted_posts);
// first() 方法返回一个值
let old_count = users.count().first::<i64>(connection);
如果自定义类型实现了 #[derive(AsChangeset)] and #[derive(Identifiable)], 则可以使用该类型的 save_changes() 方法:
foo.save_changes(&conn)
// 等效于
diesel::update(&foo).set(&foo).get_result(&conn).
子查询:
let subquery = users.select(id).filter(name.eq("Alice"));
let posts_by_alice = posts.filter(user_id.eq_any(subquery));
12 bare functions #
对于非 select query 类型, 不是直接在 table 对象上调用, diesel 在 Module diesel::dsl 中提供了相应函数(Bare functions):
- avg
- Represents a SQL AVG function. This function can only take types which are Foldable.
- case_when
- Creates a SQL CASE WHEN … END expression
copy_from
- Creates a COPY FROM statement
copy_to
- Creates a COPY TO statement
- count
- Creates a SQL COUNT expression
- count_distinct
- Creates a SQL COUNT(DISTINCT …) expression
- count_star
- Creates a SQL COUNT(*) expression
- date
- Represents the SQL DATE function. The argument should be a Timestamp expression, and the return value will be an expression of type Date.
delete
- Creates a DELETE statement.
- exists
- Creates a SQL EXISTS expression.
insert_into
- Creates an INSERT statement for the target table.
insert_or_ignore_into
- Creates an INSERT [OR] IGNORE statement.
- max
- Represents a SQL MAX function. This function can only take types which are ordered.
- min
- Represents a SQL MIN function. This function can only take types which are ordered.
- not
- Creates a SQL NOT expression
replace_into
- Creates a REPLACE statement.
select
- Creates a bare select statement, with no from clause. Primarily used for testing diesel itself, but likely useful for third party crates as well. The given expressions must be selectable from anywhere.
- sql
- Use literal SQL in the query builder.
sql_query
- Construct a full SQL query using raw SQL.
- sum
- Represents a SQL SUM function. This function can only take types which are Foldable.
update
- Creates an UPDATE statement.
高亮的这些函数在 diesel crate 中直接导出,故可以使用 diesel::update()
// update: 注意: 不是在 table 类型对象上调用该函数.
let updated_row = diesel::update(users.filter(id.eq(1)))
.set((name.eq("James"), surname.eq("Bond")))
.get_result(connection);
assert_eq!(Ok((1, "James".to_string(), "Bond".to_string())), updated_row);
// delete
let old_count = users.count().first::<i64>(connection);
diesel::delete(users.filter(id.eq(1))).execute(connection)?;
assert_eq!(old_count.map(|count| count - 1), users.count().first(connection));
// insert_into
let rows_inserted = diesel::insert_into(users)
.values(&name.eq("Sean"))
.execute(connection);
assert_eq!(Ok(1), rows_inserted);
// select (很少用,一般调用 table 的 select() 方法)
use diesel::dsl::exists;
use diesel::dsl::select;
// exists() 返回 Exists 类型实现了 Expression, 故可以作为 select() 的参数
let sean_exists = select(exists(users.filter(name.eq("Sean")))).get_result(connection);
let jim_exists = select(exists(users.filter(name.eq("Jim")))).get_result(connection);
assert_eq!(Ok(true), sean_exists);
assert_eq!(Ok(false), jim_exists);
和 table 实现的 QueryDsl 的 select() 返回的 Select 实际是 SelectStatement
类似,
diesel::dsl::select() 或 diesel::select() 函数也返回的是 SelectStatement
类型:
// select() 创建一个 select statement
pub fn select<T>(expression: T) -> select<T> where T: Expression, select<T>: AsQuery
// 返回的 select<T> 其实是 SelectStatement 的类型别名
pub type select<Selection> = SelectStatement<NoFromClause, SelectClause<Selection>>;
13 debug_query #
函数 diesel::debug_query() 用于将 QueryFragment 查询表达式打印出来(实现了 fmt::Display 和 fmt::Debug), 常用于显示 diesel 语句调试。
pub fn debug_query<DB, T>(query: &T) -> DebugQuery<'_, T, DB>
let sql = debug_query::<DB, _>(&users.count()).to_string();
assert_eq!(sql, "SELECT COUNT(*) FROM `users` -- binds: []");
let query = users.find(1);
let debug = debug_query::<DB, _>(&query);
assert_eq!(debug.to_string(), "SELECT `users`.`id`, `users`.`name` FROM `users` \
WHERE (`users`.`id` = ?) -- binds: [1]");
let debug = format!("{:?}", debug);
let expected = "Query { \
sql: \"SELECT `users`.`id`, `users`.`name` FROM `users` WHERE \
(`users`.`id` = ?)\", \
binds: [1] \
}";
assert_eq!(debug, expected);let sql = debug_query::<DB, _>(&users.count()).to_string();
assert_eq!(sql, "SELECT COUNT(*) FROM `users` -- binds: []");
let query = users.find(1);
let debug = debug_query::<DB, _>(&query);
assert_eq!(debug.to_string(), "SELECT `users`.`id`, `users`.`name` FROM `users` \
WHERE (`users`.`id` = ?) -- binds: [1]");
let debug = format!("{:?}", debug);
let expected = "Query { \
sql: \"SELECT `users`.`id`, `users`.`name` FROM `users` WHERE \
(`users`.`id` = ?)\", \
binds: [1] \
}";
assert_eq!(debug, expected);
14 associations/join #
module diesel::associations 提供了表之间 1-N 的关联关系宏和函数。
在 Child 表上使用 #[derive(Associations)] 和 #[diesel(belongs_to(Parent))] 来定义与 Parent Struct 之间的关联关系:
- 父表和子表
都要实现 Identifiable
,子表要实现 Associations 和添加 belongs_to(父表 Struct)属性宏; - #[derive(Associations)] 宏为自定义 struct 实现了 BelongsTo<Parent> 宏,后者提供 ForeiginKey 信息;
- diesel 为所有实现 Belonging<Parent> 的类型实现了 BelongingToDsl,后者提供了 belonging_to() 方法,该方法返回的对象实现了 FilterDsl trait,所以可以继续调用 QueryDsl 的其它方法;
use schema::{posts, users};
#[derive(Identifiable, Queryable, PartialEq, Debug)]
#[diesel(table_name = users)]
pub struct User {
id: i32,
name: String,
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
#[diesel(belongs_to(User))]
#[diesel(table_name = posts)]
pub struct Post {
id: i32,
user_id: i32, // Foreigin Key 惯例: table_name_id
title: String,
}
let user = users.find(2).get_result::<User>(connection)?;
// Associations 为 Post 实现了 BelongsTo,进而实现了 BelongingToDsl,所以具有 belonging_to() 方法,
// 而它返回的类型实现了 FilterDsl trait,所以可以继续使用 QueryDsl 的其它方法,如select()/filter()
// 等。
let users_post = Post::belonging_to(&user).first(connection)?;
let expected = Post { id: 3, user_id: 2, title: "My first post too".into() };
assert_eq!(expected, users_post);
Child 的 belonging_to() 方法来查询一个或多个 Parent 的 Child 记录:
- belonging_to() 是一个没有显式使用 join 方法的(join/inner_join/left_join)的
联合查询
,它的参数可以是 &Parent 或 &[Parent], 返回匹配它们的 Child列表
。 - belonging_to() 返回的对象实现了 FilterDsl::Output, 可以继续使用 QueryDsl 的其它方法,如 select()/filter()/inner_join()/left_join() 等。
let sean = users.filter(name.eq("Sean")).first::<User>(connection)?;
let tess = users.filter(name.eq("Tess")).first::<User>(connection)?;
let seans_posts = Post::belonging_to(&sean) // 返回匹配 sean 的 Post 记录列表
.select(title) // 只返回 title 字段
.load::<String>(connection)?;
assert_eq!(vec!["My first post", "About Rust"], seans_posts);
// A vec or slice can be passed as well
let more_posts = Post::belonging_to(&vec![sean, tess])
.select(title)
.load::<String>(connection)?;
assert_eq!(vec!["My first post", "About Rust", "My first post too"], more_posts);
// 多余多对多的查询,可以对 beloing_to() 返回的结果再使用 inner_join()/left_join() 等查询
let books = BookAuthor::belonging_to(&astrid_lindgren) // 返回 FilterDsl
.inner_join(books::table) // 继续调用 QueryDsl 的 inner_join()/left_join() 等方法
.select(Book::as_select());
println!("---> m_to_n_relations: {}\n", debug_query::<Pg, _>(&books).to_string());
// ---> m_to_n_relations: SELECT "books"."id", "books"."title" FROM ("books_authors" INNER JOIN "books" ON ("books_authors"."book_id" = "books"."id")) WHERE ("books_authors"."author_id" = $1) -- binds: [2]
let books = books
.load(conn)?;
println!("Asgrid Lindgren books: {books:?}");
diesel 为所有实现 IntoIterator<Item = Child> 的类型(如 Vec<Child>)实现了 diesel::associations::GroupedBy trait,该 trait 提供了方法
fn grouped_by(self, parents: &'a [Parent]) -> Vec<Vec<Self::Item>>;
由于查询结果类型 Vec<Child> 实现了 GroupedBy trait,所以需要 先调用 load() 方法获得结果,再 grouped_by()
,group_by() 的参数是 &[Parent], 返回的是按 Parent 聚合后的 Vec<Vec<Child>>,长度和
&[Parent] 一致,所以后续可以用 zip(&[Parent]) 来迭代:
- 外层 Vec 按照 &[Parent] 中每一个 Parent 排列;
- 内层 Vec<Child> 可能为空 Vec;
let users = users::table.load::<User>(connection)?;
// 需要先调用 load() 方法,对返回结果 Vec<Post> 使用 grouped_by()
let posts = Post::belonging_to(&users)
.load::<Post>(connection)? // 返回属于 users 的 Post 记录列表 Vec<Post>
.grouped_by(&users); // 对 Vec<Post> 按照 users 分组,结果是 Vec<Vec<Post>> 类型
let data = users.into_iter().zip(posts).collect::<Vec<_>>();
let expected_data = vec![
(
User { id: 1, name: "Sean".into() },
vec![
Post { id: 1, user_id: 1, title: "My first post".into() },
Post { id: 2, user_id: 1, title: "About Rust".into() },
],
),
(
User { id: 2, name: "Tess".into() },
vec![
Post { id: 3, user_id: 2, title: "My first post too".into() },
],
),
];
assert_eq!(expected_data, data);
14.1 1-N #
1-N 即 belong to 关系,module diesel::associations 提供了支撑。
创建两个表的 migration:
diesel migration generate create_books
diesel migration generate create_pages
up 语句:
CREATE TABLE books (
id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL
);
CREATE TABLE pages (
id SERIAL PRIMARY KEY,
page_number INT NOT NULL,
content TEXT NOT NULL,
book_id INTEGER NOT NULL REFERENCES books(id)
);
执行 migration:
diesel migration run
diesel::joinable!() 宏的输入参数为: child_table -> parent_table (foreign_key), 只能适用于一个 foreign_key 的情况, 对于其它情况(如 composite foreign key) 在查询时需要通过 ON clause 来指定;
// @generated automatically by Diesel CLI.
// 下面这些宏位于:https://docs.diesel.rs/master/diesel/prelude/index.html
diesel::table! {
books (id) {
id -> Int4,
title -> Varchar,
}
}
diesel::table! {
pages (id) {
id -> Int4,
page_number -> Int4,
content -> Text,
book_id -> Int4,
}
}
diesel::joinable!(pages -> books (book_id));
diesel::allow_tables_to_appear_in_same_query!(
books,
pages,
);
diesel::joinable!() 宏可以消除在关联查询时使用 ON cluase 的情况:
use schema::*;
// Child table: posts
// Parent table: users
// Foreign key: user_id (in child table: posts)
joinable!(posts -> users (user_id));
allow_tables_to_appear_in_same_query!(posts, users);
// 消除 ON clause
let implicit_on_clause = users::table.inner_join(posts::table);
let implicit_on_clause_sql = diesel::debug_query::<DB, _>(&implicit_on_clause).to_string();
// 显式使用 ON clause
let explicit_on_clause = users::table
.inner_join(posts::table.on(posts::user_id.eq(users::id)));
let explicit_on_clause_sql = diesel::debug_query::<DB, _>(&explicit_on_clause).to_string();
// 两者是等价的
assert_eq!(implicit_on_clause_sql, explicit_on_clause_sql);
// posts JOIN users ON posts.user_id = users.id
数据模型 src/model.rs:
use diesel::prelude::*;
use crate::schema::{books, pages};
#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq)]
#[diesel(table_name = books)]
pub struct Book {
pub id: i32,
pub title: String,
}
// Child 表添加 Associations
#[derive(Queryable, Selectable, Identifiable, Associations, Debug, PartialEq)]
// belongs_to 指定 Parent table, 如果是约定的 {Parent}_id 作为 FK, 则可以不指定
#[diesel(belongs_to(Book, foreign_key = book_id))]
#[diesel(table_name = pages)]
pub struct Page {
pub id: i32,
pub page_number: i32,
pub content: String,
pub book_id: i32,
}
读数据:
let momo = books::table
.filter(books::title.eq("Momo"))
.select(Book::as_select())
.get_result(conn)?;
// belonging_to() 是 1-N 关联查询, 传入的可以是单个 Parent 或多个 Parent.
let pages = Page::belonging_to(&momo)
.select(Page::as_select())
.load(conn)?; // 使用 load() 而非 execute()/get_results(), 故可以添加额外的 clause
//指定的语句: SELECT * FROM pages WHERE book_id IN(…)
println!("Pages for \"Momo\": \n {pages:?}\n");
// 查询所有的 books
let all_books = books::table.select(Book::as_select()).load(conn)?;
// get all pages for all books
let pages = Page::belonging_to(&all_books) // bool slice
.select(Page::as_select())
.load(conn)?; // 不实际查询, 可以添加额外的 clause
// group the pages per book
let pages_per_book = pages
.grouped_by(&all_books)
.into_iter()
.zip(all_books)
.map(|(pages, book)| (book, pages)) // 返回一个 tuple
.collect::<Vec<(Book, Vec<Page>)>>();
println!("Pages per book: \n {pages_per_book:?}\n");
反序列化结果到自定义类型:
// [{
// "id": 1,
// "title": "Momo",
// "pages": […],
// }]
#[derive(Serialize)] // serde 提供的 Serialize macro
struct BookWithPages {
#[serde(flatten)] // 将 book 字段内容打平到结果类型中(默认是位于 book 字段中)
book: Book,
pages: Vec<Page>,
}
// group the pages per book
let pages_per_book = pages
.grouped_by(&all_books)
.into_iter()
.zip(all_books)
.map(|(pages, book)| BookWithPages { book, pages })
.collect::<Vec<BookWithPages>>();
14.2 join #
对于非 1-N 的关联关系,diesel 需要使用 SQL JOIN 来解决。diesel 提供了两种类型 join:INNER JOIN 和 LEFT JOIN。
QueryDsl::inner_join() 用于构建 INNER JOIN 语句:
- 如果没有使用 select(), 则默认返回一个 tuple,包含双方的所有默认字段;select() 的参数可以是 tuple 或实现了 Queryable 的类型;
let page_with_book = pages::table
.inner_join(books::table)
.filter(books::title.eq("Momo"))
.select((Page::as_select(), Book::as_select()))
.load::<(Page, Book)>(conn)?;
println!("Page-Book pairs: {page_with_book:?}");
// 两种不同的 inner_join() 类型:
users::table.inner_join(posts::table.inner_join(comments::table));
// Results in the following SQL
// SELECT * FROM users
// INNER JOIN posts ON users.id = posts.user_id
// INNER JOIN comments ON post.id = comments.post_id
users::table.inner_join(posts::table).inner_join(comments::table);
// Results in the following SQL
// SELECT * FROM users
// INNER JOIN posts ON users.id = posts.user_id
// INNER JOIN comments ON users.id = comments.user_id
left_join():
- 返回的结果类型是:(Book, Option<Page>)
let book_without_pages = books::table
.left_join(pages::table)
.select((Book::as_select(), Option::<Page>::as_select()))
.load::<(Book, Option<Page>)>(conn)?;
println!("Book-Page pairs (including empty books): {book_without_pages:?}");
默认使用 joinable!() 来隐式构建 ON 语句,也可以使用 JoinOnDsl::on() 来指定自定义 join 规则:
pub trait JoinOnDsl: Sized {
// Provided method
fn on<On>(self, on: On) -> On<Self, On> { ... }
}
let data = users::table
.left_join(posts::table.on(
users::id.eq(posts::user_id).and(
posts::title.eq("My first post"))
))
.select((users::name, posts::title.nullable()))
.load(connection);
let expected = vec![
("Sean".to_string(), Some("My first post".to_string())),
("Tess".to_string(), None),
];
assert_eq!(Ok(expected), data);
14.3 M-N #
Many-to-Many 需要使用 Join Table 来实现,它 belongs_to 所有关联的 table。
创建 migration:
diesel migration generate create_authors
diesel migration generate create_books_authors // 创建 join table
join table 的定义中要使用 FK 来引用关联的表记录:
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL
);
CREATE TABLE books_authors (
book_id INTEGER REFERENCES books(id),
author_id INTEGER REFERENCES authors(id),
PRIMARY KEY(book_id, author_id)
);
执行迁移:diesel migration run
创建 model:
- 核心是 join table 要 belongs_to 到所有的关联的表;
use diesel::prelude::*;
use crate::schema::{books, pages, authors, books_authors};
#[derive(Queryable, Selectable, Identifiable, PartialEq, Debug)]
#[diesel(table_name = authors)]
pub struct Author {
pub id: i32,
pub name: String,
}
#[derive(Identifiable, Selectable, Queryable, Associations, Debug)]
#[diesel(belongs_to(Book))]
#[diesel(belongs_to(Author))]
#[diesel(table_name = books_authors)]
#[diesel(primary_key(book_id, author_id))]
pub struct BookAuthor {
pub book_id: i32,
pub author_id: i32,
}
读数据:
- 先使用 belonging_to() 来进行 1-N join,然后再使用 inner_join();
// 多余多对多的查询,可以对 beloing_to() 返回的结果再使用 inner_join()/left_join() 等查询
let books = BookAuthor::belonging_to(&astrid_lindgren) // 返回 FilterDsl
.inner_join(books::table) // 继续调用 QueryDsl 的 inner_join()/left_join() 等方法
.select(Book::as_select());
println!("---> m_to_n_relations: {}\n", debug_query::<Pg, _>(&books).to_string());
// ---> m_to_n_relations: SELECT "books"."id", "books"."title" FROM ("books_authors" INNER JOIN "books" ON ("books_authors"."book_id" = "books"."id")) WHERE ("books_authors"."author_id" = $1) -- binds: [2]
let books = books .load(conn)?;
println!("Asgrid Lindgren books: {books:?}");
// 例子
let astrid_lindgren = authors::table
.filter(authors::name.eq("Astrid Lindgren"))
.select(Author::as_select())
.get_result(conn)?;
// get all of Astrid Lindgren's books
let books = BookAuthor::belonging_to(&astrid_lindgren)
.inner_join(books::table)
.select(Book::as_select())
.load(conn)?;
println!("Books by Astrid Lindgren: {books:?}");
14.4 示例 #
SQL 表名惯例: 小写的复数形式, 用下划线分割: books, authors, books_authros;
对应的 Rust model struct 命名惯例: 大写的单数形式, Book, Author, BookAuthor;
表的主键: id
表的外键: 单数表名_id, 如: book_id, author_id;
relations:
// https://github.com/diesel-rs/diesel/tree/2.2.x/examples/postgres/relations
// CREATE TABLE books (
// id SERIAL PRIMARY KEY,
// title VARCHAR NOT NULL
// );
// CREATE TABLE pages (
// id SERIAL PRIMARY KEY,
// page_number INT NOT NULL,
// content TEXT NOT NULL,
// book_id INTEGER NOT NULL REFERENCES books(id)
// );
// CREATE TABLE authors (
// id SERIAL PRIMARY KEY,
// name VARCHAR NOT NULL
// );
// CREATE TABLE books_authors (
// book_id INTEGER REFERENCES books(id),
// author_id INTEGER REFERENCES authors(id),
// PRIMARY KEY(book_id, author_id)
// );
// @generated automatically by Diesel CLI.
diesel::table! {
authors (id) { // table_name (primary_key_column)
id -> Int4,
name -> Varchar,
}
}
diesel::table! {
books (id) {
id -> Int4,
title -> Varchar,
}
}
diesel::table! {
books_authors (book_id, author_id) {
book_id -> Int4, // 只是表字段, 没有体现外键引用关系.
author_id -> Int4,
}
}
diesel::table! {
pages (id) {
id -> Int4,
page_number -> Int4,
content -> Text,
book_id -> Int4,
}
}
// 定义表之间的外键引用关系
// 子表 -> 父表 (子表中引用父表的外键)
diesel::joinable!(books_authors -> authors (author_id));
diesel::joinable!(books_authors -> books (book_id));
diesel::joinable!(pages -> books (book_id));
diesel::allow_tables_to_appear_in_same_query!(authors, books, books_authors, pages,);
// https://github.com/diesel-rs/diesel/blob/2.2.x/examples/postgres/relations/src/model.rs
#[derive(Queryable, Selectable, Identifiable, PartialEq, Debug, Clone)]
#[diesel(table_name = authors)] // table_name 一般用复数, struct 用单数.
pub struct Author {
pub id: i32,
pub name: String,
}
// 子表(带外键) 需要:
// 1. 实现 Associations 宏;
// 2. 添加 belongs_to(父表 Struct) 的属性宏
// 3. 父表和子表都需要实现 Identifiable
#[derive(Identifiable, Selectable, Queryable, Associations, Debug, Clone)]
#[diesel(belongs_to(Book))] // 参数是父表 Struct 名称
#[diesel(belongs_to(Author))]
#[diesel(table_name = books_authors)]
#[diesel(primary_key(book_id, author_id))] // 使用非默认的主键(id), 需要明确指定, 这里为联合主键.
pub struct BookAuthor {
pub book_id: i32,
pub author_id: i32,
}
#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq, Clone)]
#[diesel(table_name = books)]
pub struct Book {
pub id: i32,
pub title: String,
}
#[derive(Queryable, Selectable, Identifiable, Associations, Debug, PartialEq)]
#[diesel(belongs_to(Book))]
#[diesel(table_name = pages)]
pub struct Page {
pub id: i32,
pub page_number: i32,
pub content: String,
pub book_id: i32,
}
fn new_author(conn: &mut PgConnection, name: &str) -> Result<Author, Box<dyn Error + Send + Sync>> {
let author1 = diesel::insert_into(authors::table)
.values(authors::name.eq(name))
.returning(Author::as_returning()); // insert/update 使用 returning() 来返回值类型
println!("---> new_author: {}", debug_query::<Pg, _>(&author1).to_string());
// ---> new_author: INSERT INTO "authors" ("name") VALUES ($1) RETURNING "authors"."id", "authors"."name" -- binds: ["Michael Ende"]
// ---> new_author: INSERT INTO "authors" ("name") VALUES ($1) RETURNING "authors"."id", "authors"."name" -- binds: ["Astrid Lindgren"]
let author = author1
.get_result(conn)?;
Ok(author)
}
fn new_book(conn: &mut PgConnection, title: &str) -> Result<Book, Box<dyn Error + Send + Sync>> {
let book = diesel::insert_into(books::table)
.values(books::title.eq(title))
.returning(Book::as_returning())
.get_result(conn)?;
Ok(book)
}
fn new_books_author(
conn: &mut PgConnection,
book_id: i32,
author_id: i32,
) -> Result<BookAuthor, Box<dyn Error + Send + Sync>> {
// 对于关联表, 需要插入两个 id
let book_author = diesel::insert_into(books_authors::table)
.values(( // 一个 tuple 对应一个记录的多个字段
books_authors::book_id.eq(book_id),
books_authors::author_id.eq(author_id),
))
.returning(BookAuthor::as_returning());
println!("---> new_books_author: {}", debug_query::<Pg, _>(&book_author).to_string());
// ---> new_books_author: INSERT INTO "books_authors" ("book_id", "author_id") VALUES ($1, $2) RETURNING "books_authors"."book_id", "books_authors"."author_id" -- binds: [17, 12]
// ---> new_books_author: INSERT INTO "books_authors" ("book_id", "author_id") VALUES ($1, $2) RETURNING "books_authors"."book_id", "books_authors"."author_id" -- binds: [18, 12]
// ---> new_books_author: INSERT INTO "books_authors" ("book_id", "author_id") VALUES ($1, $2) RETURNING "books_authors"."book_id", "books_authors"."author_id" -- binds: [18, 11]
let book_author = book_author
.get_result(conn)?;
Ok(book_author)
}
fn new_page(
conn: &mut PgConnection,
page_number: i32,
content: &str,
book_id: i32,
) -> Result<Page, Box<dyn Error + Send + Sync>> {
let page = diesel::insert_into(pages::table)
.values((
pages::page_number.eq(page_number),
pages::content.eq(content),
pages::book_id.eq(book_id),
))
.returning(Page::as_returning())
.get_result(conn)?;
Ok(page)
}
fn joins(conn: &mut PgConnection) -> Result<(), Box<dyn Error + Send + Sync>> {
// innert_join 查询: 查询所有页
let page_with_book = pages::table
.inner_join(books::table) // inner_join 返回两个表匹配的交集,所以返回的记录中肯定同时有 Page 和 Book
.filter(books::title.eq("Momo"))
.select((Page::as_select(), Book::as_select()));
println!("---> joins: {}", debug_query::<Pg, _>(&page_with_book).to_string());
// ---> joins: SELECT "pages"."id", "pages"."page_number", "pages"."content", "pages"."book_id", "books"."id", "books"."title" FROM ("pages" INNER JOIN "books" ON ("pages"."book_id" = "books"."id")) WHERE ("books"."title" = $1) -- binds: ["Momo"]
let page_with_book = page_with_book
.load::<(Page, Book)>(conn)?;
println!("Page-Book pairs: {page_with_book:?}");
let book_without_pages = books::table
.left_join(pages::table)
.select((Book::as_select(), Option::<Page>::as_select())) // left_join/right_join() 返回的记录中,另一份不一定能查到值,所以需要使用 Option<T>。
.load::<(Book, Option<Page>)>(conn)?;
println!("Book-Page pairs (including empty books): {book_without_pages:?}");
Ok(())
}
fn one_to_n_relations(conn: &mut PgConnection) -> Result<(), Box<dyn Error + Send + Sync>> {
let momo = books::table
.filter(books::title.eq("Momo"))
.select(Book::as_select())
.get_result(conn)?;
// get pages for the book "Momo"
let pages = Page::belonging_to(&momo)
.select(Page::as_select());
println!("---> one_to_n_relations: {}\n", debug_query::<Pg, _>(&pages).to_string());
// ---> one_to_n_relations: SELECT "pages"."id", "pages"."page_number", "pages"."content", "pages"."book_id" FROM "pages" WHERE ("pages"."book_id" = $1) -- binds: [1]
let pages = pages
.load(conn)?;
println!("Pages for \"Momo\": \n {pages:?}\n");
let mut all_books = books::table.select(Book::as_select()).load(conn)?;
// get all pages for all books
let pages = Page::belonging_to(&all_books) // 一次 select ANY 查询:从 all_books 提取主键列表,然后使用 Page 的外键来 ANY 查询。
.select(Page::as_select());
println!("---> one_to_n_relations2: {}\n", debug_query::<Pg, _>(&pages).to_string());
// ---> one_to_n_relations2: SELECT "pages"."id", "pages"."page_number", "pages"."content", "pages"."book_id" FROM "pages" WHERE ("pages"."book_id" = ANY($1)) -- binds: [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]]
let pages = pages
.load(conn)?;
println!("---> pages: {:?}", pages);
// ---> pages: [Page { id: 1, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 1 }, Page { id: 2, page_number: 2, content: "den prachtvollen Theatern...", book_id: 1 }, Page { id: 3, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 4 }, Page { id: 4, page_number: 2, content: "den prachtvollen Theatern...", book_id: 4 }, Page { id: 5, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 7 }, Page { id: 6, page_number: 2, content: "den prachtvollen Theatern...", book_id: 7 }, Page { id: 7, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 10 }, Page { id: 8, page_number: 2, content: "den prachtvollen Theatern...", book_id: 10 }, Page { id: 9, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 13 }, Page { id: 10, page_number: 2, content: "den prachtvollen Theatern...", book_id: 13 }, Page { id: 11, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 16 }, Page { id: 12, page_number: 2, content: "den prachtvollen Theatern...", book_id: 16 }, Page { id: 13, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 19 }, Page { id: 14, page_number: 2, content: "den prachtvollen Theatern...", book_id: 19 }, Page { id: 15, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 22 }, Page { id: 16, page_number: 2, content: "den prachtvollen Theatern...", book_id: 22 }, Page { id: 17, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 25 }, Page { id: 18, page_number: 2, content: "den prachtvollen Theatern...", book_id: 25 }, Page { id: 19, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 28 }, Page { id: 20, page_number: 2, content: "den prachtvollen Theatern...", book_id: 28 }]
// 抛出异常:no entry found for key
// all_books = vec![Book{id: 333, title: "abcd".into()}];
// 故意加一个不存在的 book
all_books.push(Book{id: 333, title: "abcd".into()});
let pages_per_book = pages
.grouped_by(&all_books) // 返回的 Vec 长度和 all_books 一致,Vec 的元素类型为 Vec<Page>(可能为空)
.into_iter()
.collect::<Vec<_>>();
println!("Pages per book: \n {pages_per_book:?}\n");
// 注意: 返回的 Vec 类型是 Vec<Vec<Page>>, 长度和 all_books 一致, 外层的 Vec 元素按照 book_id 排序, 如果 book_id 对应的 Page 不存在, 则内层的 Vec<Page> 为空 [];
// Pages per book:
// [[Page { id: 1, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 1 }, Page { id: 2, page_number: 2, content: "den prachtvollen Theatern...", book_id: 1 }], [], [], [Page { id: 3, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 4 }, Page { id: 4, page_number: 2, content: "den prachtvollen Theatern...", book_id: 4 }], [], [], [Page { id: 5, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 7 }, Page { id: 6, page_number: 2, content: "den prachtvollen Theatern...", book_id: 7 }], [], [], [Page { id: 7, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 10 }, Page { id: 8, page_number: 2, content: "den prachtvollen Theatern...", book_id: 10 }], [], [], [Page { id: 9, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 13 }, Page { id: 10, page_number: 2, content: "den prachtvollen Theatern...", book_id: 13 }], [], [], [Page { id: 11, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 16 }, Page { id: 12, page_number: 2, content: "den prachtvollen Theatern...", book_id: 16 }], [], [], [Page { id: 13, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 19 }, Page { id: 14, page_number: 2, content: "den prachtvollen Theatern...", book_id: 19 }], [], [], [Page { id: 15, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 22 }, Page { id: 16, page_number: 2, content: "den prachtvollen Theatern...", book_id: 22 }], [], [], [Page { id: 17, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 25 }, Page { id: 18, page_number: 2, content: "den prachtvollen Theatern...", book_id: 25 }], [], [], [Page { id: 19, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 28 }, Page { id: 20, page_number: 2, content: "den prachtvollen Theatern...", book_id: 28 }], [], [], [Page { id: 21, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 31 }, Page { id: 22, page_number: 2, content: "den prachtvollen Theatern...", book_id: 31 }], [], [], [Page { id: 23, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 34 }, Page { id: 24, page_number: 2, content: "den prachtvollen Theatern...", book_id: 34 }], [], [], [Page { id: 25, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 37 }, Page { id: 26, page_number: 2, content: "den prachtvollen Theatern...", book_id: 37 }], [], [], [Page { id: 27, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 40 }, Page { id: 28, page_number: 2, content: "den prachtvollen Theatern...", book_id: 40 }], [], [], [Page { id: 29, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 43 }, Page { id: 30, page_number: 2, content: "den prachtvollen Theatern...", book_id: 43 }], [], [], []]
// group the pages per book
// let pages_per_book = pages
// .grouped_by(&all_books) // 返回的 Vec 长度和 all_books 一致,Vec 的元素类型为 Vec<Page>(可能为空)
// .into_iter()
// .zip(all_books) // 由于 grouped_by() 返回的长度和 all_books 一致,所以可以 zip(all_books)
// .map(|(pages, book)| (book, pages))
// .collect::<Vec<(Book, Vec<Page>)>>();
// println!("Pages per book: \n {pages_per_book:?}\n");
// 注意: 返回所有 all_books 中的 Book, 但是 Vec<Page> 可能为空列表,如上面 id 为 333 的 Book
// [(Book { id: 1, title: "Momo" }, [Page { id: 1, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 1 }, Page { id: 2, page_number: 2, content: "den prachtvollen Theatern...", book_id: 1 }]), (Book { id: 2, title: "Pippi Långstrump" }, []), (Book { id: 3, title: "Pippi and Momo" }, []), (Book { id: 4, title: "Momo" }, [Page { id: 3, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 4 }, Page { id: 4, page_number: 2, content: "den prachtvollen Theatern...", book_id: 4 }]), (Book { id: 5, title: "Pippi Långstrump" }, []), (Book { id: 6, title: "Pippi and Momo" }, []), (Book { id: 7, title: "Momo" }, [Page { id: 5, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 7 }, Page { id: 6, page_number: 2, content: "den prachtvollen Theatern...", book_id: 7 }]), (Book { id: 8, title: "Pippi Långstrump" }, []), (Book { id: 9, title: "Pippi and Momo" }, []), (Book { id: 10, title: "Momo" }, [Page { id: 7, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 10 }, Page { id: 8, page_number: 2, content: "den prachtvollen Theatern...", book_id: 10 }]), (Book { id: 11, title: "Pippi Långstrump" }, []), (Book { id: 12, title: "Pippi and Momo" }, []), (Book { id: 13, title: "Momo" }, [Page { id: 9, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 13 }, Page { id: 10, page_number: 2, content: "den prachtvollen Theatern...", book_id: 13 }]), (Book { id: 14, title: "Pippi Långstrump" }, []), (Book { id: 15, title: "Pippi and Momo" }, []), (Book { id: 16, title: "Momo" }, [Page { id: 11, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 16 }, Page { id: 12, page_number: 2, content: "den prachtvollen Theatern...", book_id: 16 }]), (Book { id: 17, title: "Pippi Långstrump" }, []), (Book { id: 18, title: "Pippi and Momo" }, []), (Book { id: 19, title: "Momo" }, [Page { id: 13, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 19 }, Page { id: 14, page_number: 2, content: "den prachtvollen Theatern...", book_id: 19 }]), (Book { id: 20, title: "Pippi Långstrump" }, []), (Book { id: 21, title: "Pippi and Momo" }, []), (Book { id: 22, title: "Momo" }, [Page { id: 15, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 22 }, Page { id: 16, page_number: 2, content: "den prachtvollen Theatern...", book_id: 22 }]), (Book { id: 23, title: "Pippi Långstrump" }, []), (Book { id: 24, title: "Pippi and Momo" }, []), (Book { id: 25, title: "Momo" }, [Page { id: 17, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 25 }, Page { id: 18, page_number: 2, content: "den prachtvollen Theatern...", book_id: 25 }]), (Book { id: 26, title: "Pippi Långstrump" }, []), (Book { id: 27, title: "Pippi and Momo" }, []), (Book { id: 28, title: "Momo" }, [Page { id: 19, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 28 }, Page { id: 20, page_number: 2, content: "den prachtvollen Theatern...", book_id: 28 }]), (Book { id: 29, title: "Pippi Långstrump" }, []), (Book { id: 30, title: "Pippi and Momo" }, []), (Book { id: 31, title: "Momo" }, [Page { id: 21, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 31 }, Page { id: 22, page_number: 2, content: "den prachtvollen Theatern...", book_id: 31 }]), (Book { id: 32, title: "Pippi Långstrump" }, []), (Book { id: 33, title: "Pippi and Momo" }, []), (Book { id: 34, title: "Momo" }, [Page { id: 23, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 34 }, Page { id: 24, page_number: 2, content: "den prachtvollen Theatern...", book_id: 34 }]), (Book { id: 35, title: "Pippi Långstrump" }, []), (Book { id: 36, title: "Pippi and Momo" }, []), (Book { id: 37, title: "Momo" }, [Page { id: 25, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 37 }, Page { id: 26, page_number: 2, content: "den prachtvollen Theatern...", book_id: 37 }]), (Book { id: 38, title: "Pippi Långstrump" }, []), (Book { id: 39, title: "Pippi and Momo" }, []), (Book { id: 40, title: "Momo" }, [Page { id: 27, page_number: 1, content: "In alten, alten Zeiten ...", book_id: 40 }, Page { id: 28, page_number: 2, content: "den prachtvollen Theatern...", book_id: 40 }]), (Book { id: 41, title: "Pippi Långstrump" }, []), (Book { id: 42, title: "Pippi and Momo" }, []), (Book { id: 333, title: "abcd" }, [])]
Ok(())
}
// 基于关联表的查询
// 1. 关联表的 belonging_to(父表记录) 方法返回附表记录关联的所有关联表记录
// 2. inner_join(books::table) 返回属于 astrid_lindgren 的 Book 记录
fn m_to_n_relations(conn: &mut PgConnection) -> Result<(), Box<dyn Error + Send + Sync>> {
let astrid_lindgren = authors::table
.filter(authors::name.eq("Astrid Lindgren"))
.select(Author::as_select())
.get_result(conn)?;
// get all of Astrid Lindgren's books
let books = BookAuthor::belonging_to(&astrid_lindgren)
.inner_join(books::table)
.select(Book::as_select());
println!("---> m_to_n_relations: {}\n", debug_query::<Pg, _>(&books).to_string());
// ---> m_to_n_relations: SELECT "books"."id", "books"."title" FROM ("books_authors" INNER JOIN "books" ON ("books_authors"."book_id" = "books"."id")) WHERE ("books_authors"."author_id" = $1) -- binds: [2]
let books = books
.load(conn)?;
println!("Asgrid Lindgren books: {books:?}");
let collaboration = books::table
.filter(books::title.eq("Pippi and Momo"))
.select(Book::as_select())
.get_result(conn)?;
// get authors for the collaboration
let authors = BookAuthor::belonging_to(&collaboration)
.inner_join(authors::table)
.select(Author::as_select())
.load(conn)?;
println!("Authors for \"Pipi and Momo\": {authors:?}");
// get a list of authors with all their books
let all_authors = authors::table.select(Author::as_select()).load(conn)?;
let books = BookAuthor::belonging_to(&authors)
.inner_join(books::table)
.select((BookAuthor::as_select(), Book::as_select()));
println!("---> m_to_n_relations2: {}\n", debug_query::<Pg, _>(&books).to_string());
// ---> m_to_n_relations2: SELECT "books_authors"."book_id", "books_authors"."author_id", "books"."id", "books"."title" FROM ("books_authors" INNER JOIN "books" ON ("books_authors"."book_id" = "books"."id")) WHERE ("books_authors"."author_id" = ANY($1)) -- binds: [[2, 1]]
let books = books
.load(conn)?;
let books_per_author: Vec<(Author, Vec<Book>)> = books
.grouped_by(&all_authors)
.into_iter()
.zip(authors)
.map(|(b, author)| (author, b.into_iter().map(|(_, book)| book).collect()))
.collect();
println!("All authors including their books: {books_per_author:?}");
Ok(())
}
15 delete #
diesel::delete() 返回 DeleteStatement, 传入的参数类型是 IntoUpdateTarget:
- Identifiable/table/SelectStatement 均实现了 IntoUpdateTarget,所以 tables 和 tables 调用的各种 QueryDsl 方法可以为传给 delete()
- 当传入 Identifiable/table 时,删除整个 table 记录。可以传入 SelectStatement 或调用 DeleteStatement 的 filter方法来限制删除的记录范围。
- Identifiable 可以通过 derive macro 为自定义 struct 类型来实现,这样可以 delete() 可以传入自定义类型。
// Function diesel::dsl::delete
pub fn delete<T: IntoUpdateTarget>( source: T, ) -> DeleteStatement<T::Table, T::WhereClause>
// A type which can be passed to update or delete.
pub trait IntoUpdateTarget: HasTable {
type WhereClause;
// Required method
fn into_update_target(self) -> UpdateTarget<Self::Table, Self::WhereClause>;
}
// Struct diesel::query_builder::UpdateTarget
pub struct UpdateTarget<Table, WhereClause> {
pub table: Table,
pub where_clause: WhereClause,
}
// 1. Identifiable 实现了 IntoUpdateTarget,故可以传入实现了 Identifiable 的自定义 struct 类型
impl<T, Tab, V> IntoUpdateTarget for T
where
T: Identifiable<Table = Tab>,
Tab: Table + FindDsl<T::Id>,
Find<Tab, T::Id>: IntoUpdateTarget<Table = Tab, WhereClause = V>
// 2. table 实现了 IntoUpdateTarget
impl IntoUpdateTarget for table
// 3. SelectStatement 也实现了 IntoUpdateTarget (但是奇怪的是,生成的 docs 并没有显示)
// https://docs.diesel.rs/2.2.x/src/diesel/query_builder/select_statement/dsl_impls.rs.html#540
impl<F, W> IntoUpdateTarget
for SelectStatement<FromClause<F>, DefaultSelectClause<FromClause<F>>, NoDistinctClause, W>
where F: QuerySource, Self: HasTable, W: ValidWhereClause<F>,
{
type WhereClause = W;
fn into_update_target(self) -> UpdateTarget<Self::Table, Self::WhereClause> {
UpdateTarget {
table: Self::table(),
where_clause: self.where_clause,
}
}
}
// 示例
let deleted_rows = diesel::delete(users) // 传入实现 IntoUpdateTarget 的 table
.filter(name.eq("Sean"))
.execute(connection);
assert_eq!(Ok(1), deleted_rows);
let old_count = users.count().first::<i64>(connection);
diesel::delete(users.filter(id.eq(1))) // 传入实现 IntoUpdateTarget 的 SelectStatement
.execute(connection)?;
assert_eq!(old_count.map(|count| count - 1), users.count().first(connection));
// 删除单个记录
let old_count = users.count().first::<i64>(connection);
diesel::delete(users.filter(id.eq(1))).execute(connection)?;
assert_eq!(old_count.map(|count| count - 1), users.count().first(connection));
// 删除整个表
diesel::delete(users).execute(connection)?;
assert_eq!(Ok(0), users.count().first::<i64>(connection));
Struct diesel::query_builder::DeleteStatement:
- DeleteStatement 实现了 filter/or_filter/returning() 方法;
- returning() 的参数 SelectableExpression,table field 和它的各种 tuple 类型实现了该 trait。
pub struct DeleteStatement<T: QuerySource, U, Ret = NoReturningClause> { /* private fields */ }
// DeleteStatement 实现了 filter/or_filter/returning 方法
impl<T: QuerySource, U> DeleteStatement<T, U, NoReturningClause>
pub fn filter<Predicate>(self, predicate: Predicate) -> Filter<Self, Predicate>
where Self: FilterDsl<Predicate>
// Calling foo.filter(bar).or_filter(baz) is identical to foo.filter(bar.or(baz)).
pub fn or_filter<Predicate>( self, predicate: Predicate, ) -> OrFilter<Self, Predicate>
where Self: OrFilterDsl<Predicate>,
// table!() 宏生成的 filed 字段类型都实现了 SelectableExpression,可以用作 returning 的参数。
//
// 如果要返回多个字段,则需要使用 tuple 类型。
pub fn returning<E>( self, returns: E, ) -> DeleteStatement<T, U, ReturningClause<E>>
where E: SelectableExpression<T>, DeleteStatement<T, U, ReturningClause<E>>: Query,
// 示例
let deleted_rows = diesel::delete(users)
.filter(name.eq("Sean"))
.execute(connection);
assert_eq!(Ok(1), deleted_rows);
let expected_names = vec!["Tess".to_string()];
let names = users.select(name).load(connection);
assert_eq!(Ok(expected_names), names);
let deleted_rows = diesel::delete(users)
.filter(name.eq("Sean"))
.or_filter(name.eq("Tess"))
.execute(connection);
assert_eq!(Ok(2), deleted_rows);
let num_users = users.count().first(connection);
assert_eq!(Ok(0), num_users);
delete:
let target = args().nth(1).expect("Expected a target to match against");
let pattern = format!("%{target}%");
let connection = &mut establish_connection();
let num_deleted = diesel::delete(posts.filter(title.like(pattern)))
.execute(connection)
.expect("Error deleting posts");
println!("Deleted {num_deleted} posts");
16 insert #
diesel::insert_into() 或 diesel::dsl::insert_into() 函数:
- 返回的对象实现了 default_values() 和 values() 方法。
- values() 方法的参数是 Insertable, table/Eq/Grouped/Vec/Option/[T;N] 均实现了它。
// target 参数类型是 table
pub fn insert_into<T: Table>(target: T) -> IncompleteInsertStatement<T>
// IncompleteInsertStatement 实现了 default_values() 和 values() 方法:
pub fn default_values(self) -> InsertStatement<T, DefaultValues, Op>
pub fn values<U>(self, records: U) -> InsertStatement<T, U::Values, Op> where U: Insertable<T>,
// table/Eq/Grouped/Vec/Option/[T;N] 也均实现了 Insertable,也可以作为 values() 的参数,实现一次插
// 入多条记录。
impl<'a, F, S, D, W, O, LOf, G, H, LC, Tab> Insertable<Tab> for &'a SelectStatement
impl<T> Insertable<T> for table
impl<T, Tab> Insertable<Tab> for Vec<T>
impl<T, Tab, V> Insertable<Tab> for Option<T>
impl<T, Tab, const N: usize> Insertable<Tab> for [T; N]
impl<T, Tab, const N: usize> Insertable<Tab> for Box<[T; N]>
如果表格所有字段都有缺省值,调用 default_values() 方法:
- 如果 SQL 字段可以为 NULL(没有加 NOT NULL)但没有设置 DEFAULT,则默认插入 NULL;
use schema::users::dsl::*;
insert_into(users).default_values().execute(conn)
// 等效于: INSERT INTO "users" DEFAULT VALUES -- binds: []
diesel::sql_query("CREATE TABLE users (
name VARCHAR(255) NOT NULL DEFAULT 'Sean',
hair_color VARCHAR(255) NOT NULL DEFAULT 'Green'
)").execute(connection)?;
insert_into(users) // users 是 Table
.default_values()
.execute(connection)
.unwrap();
let inserted_user = users.first(connection)?;
let expected_data = (String::from("Sean"), String::from("Green"));
assert_eq!(expected_data, inserted_user);
如果要插入指定的值, 使用 values() 方法:
- values() 方法的参数类型是
Insertable
:table/&Eq/&Grouped/Vec/&[T]/tuple 均实现了它,也可以使用 derive macro 来为自定义 struct 实现它。 - 插入单条记录:使用 tuple 来指定多个字段;
- 插入多条记录:使用 Vec<T> 或 &[T], 其中 T 也可以是 tuple。
- #[derive(Insertable)] 修饰的 struct 类型也实现了它,所以可以传入自定义 struct 类型;
let rows_inserted = diesel::insert_into(users) // users 是 Table
.values(&name.eq("Sean")) // &Eq/&Grouped 实现了 Insertable, 插入单个字段的记录
.execute(connection);
assert_eq!(Ok(1), rows_inserted);
// tuple 也实现了 Insertable, 插入单个记录的多个字段
let new_user = (id.eq(1), name.eq("Sean"));
let rows_inserted = diesel::insert_into(users)
.values(&new_user)
.execute(connection);
assert_eq!(Ok(1), rows_inserted);
// Vec 也实现了 Insertable,可以一次插入多个记录,每个记录的类型可以是:
// 1. Eq/Grouped: 插入单个字段
let new_users = vec![
name.eq("Tess"),
name.eq("Jim"),
];
let rows_inserted = diesel::insert_into(users)
.values(&new_users)
.execute(connection);
assert_eq!(Ok(2), rows_inserted);
// 2. tuple:插入多个字段
let new_users = vec![
(id.eq(2), name.eq("Tess")),
(id.eq(3), name.eq("Jim")),
];
let rows_inserted = diesel::insert_into(users)
.values(&new_users)
.execute(connection);
assert_eq!(Ok(2), rows_inserted);
// Insertable struct 可以作为 values() 的参数, 实现一次更新多个字段值
#[derive(Insertable)]
#[diesel(table_name = users)]
struct NewUser<'a> {
name: &'a str,
}
// Insert one record at a time
let new_user = NewUser { name: "Ruby Rhod" };
diesel::insert_into(users)
.values(&new_user)
.execute(connection)
.unwrap();
// Insert many records
let new_users = vec![
NewUser { name: "Leeloo Multipass" },
NewUser { name: "Korben Dallas" },
];
let inserted_names = diesel::insert_into(users)
.values(&new_users)
.execute(connection)
.unwrap();
插入列的缺省值:
- SQL 字段类型没有明确设置 NOT NULL 时,生成的 table!() 字段是 Nullable<T>, 对应的 Rust struct
model 字段值应该设置为 Option<T>:当将它设置为 None 时,diesel 插入缺省值;
- 如果 SQL schema 没有通过 DEFAULT 来设置缺省值且也没有设置 NOT NULL, 则缺省值为 NULL;
- 如果 SQL schema 设置了 DEFAULT,则使用对应的缺省值;
// The column color in brands table is NOT NULL DEFAULT 'Green'.
#[derive(Insertable)]
#[diesel(table_name = brands)]
struct NewBrand {
color: Option<String>, // 字段类型为 Option<T> 时,如果设置为 None,则 diesel 插入 T 缺省值。
}
// 插入传入的值
let new_brand = NewBrand { color: Some("Red".into()) };
// 插入缺省值
let new_brand = NewBrand { color: None }; // 设置为 None
diesel::insert_into(brands)
.values(&new_brand)
.execute(connection)
.unwrap();
对于可以为 NULL 的列插入缺省值或 NULL 值:
- 可以为 NULL 的列需要使用 Option<Option<T>> 类型值;
- 如果插入 None,则使用缺省值;
- 如果插入 Some(None) 则使用 NULL 值;
- 如果插入 Some(Some(T)) 则插入 T 值;
#[derive(Insertable)]
#[diesel(table_name = brands)]
struct NewBrand {
accent: Option<Option<String>>,
}
// Insert `Red`
let new_brand = NewBrand { accent: Some(Some("Red".into())) };
diesel::insert_into(brands)
.values(&new_brand)
.execute(connection)
.unwrap();
// Insert the default accent
let new_brand = NewBrand { accent: None };
diesel::insert_into(brands)
.values(&new_brand)
.execute(connection)
.unwrap();
// Insert `NULL`
let new_brand = NewBrand { accent: Some(None) };
diesel::insert_into(brands)
.values(&new_brand)
.execute(connection)
.unwrap();
插入 select 的值:使用 into_columns() 来指定 select 出来的各列应该插入到表的哪些列;
let new_posts = users::table
.select((
users::name.concat("'s First Post"),
users::id,
));
diesel::insert_into(posts::table)
.values(new_posts)
.into_columns((posts::title, posts::user_id)) // values() 的列值应该插入到本表的那些列?
.execute(conn)?;
let inserted_posts = posts::table
.select(posts::title)
.load::<String>(conn)?;
let expected = vec!["Sean's First Post", "Tess's First Post"];
assert_eq!(expected, inserted_posts);
default_values()/values() 返回的类型是 Struct diesel::query_builder::InsertStatement:
- 方法:into_columns()/returning()/on_conflict_do_nothing()/on_conflict();
pub fn new(target: T, records: U, operator: Op, returning: Ret) -> Self
// 子查询插入方式
pub fn into_columns<C2>( self, columns: C2, ) -> InsertStatement
// 返回插入成功的记录的字段(如 id)
pub fn returning<E>( self, returns: E,) -> InsertStatement
// 冲突检测
pub fn on_conflict_do_nothing( self, ) -> InsertStatement
pub fn on_conflict<Target>( self, target: Target, ) -> IncompleteOnConflict
// 实现了 ExecuteDsl 和 RunQueryDsl,执行实际的 SQL 操作。
impl<V, T, QId, C, Op, O, const STATIC_QUERY_ID: bool> ExecuteDsl<C, Sqlite> for InsertStatement
impl<T: QuerySource, U, Op, Ret, Conn> RunQueryDsl<Conn> for InsertStatement
impl<T: Copy + QuerySource, U: Copy, Op: Copy, Ret: Copy> Copy for InsertStatement
on_conflict_do_nothing():
let user = User { id: 1, name: "Sean" };
let user_count = users.count().get_result::<i64>(conn)?;
assert_eq!(user_count, 0);
diesel::insert_into(users)
.values(&user)
.on_conflict_do_nothing() // 冲突时什么多不做,不实际插入
.execute(conn)?;
let user_count = users.count().get_result::<i64>(conn)?;
assert_eq!(user_count, 1);
// 插入一个 vec,可能只插入一部分记录。
let user = User { id: 1, name: "Sean" };
let inserted_row_count = diesel::insert_into(users)
.values(&vec![user, user])
.on_conflict_do_nothing()
.execute(conn)?;
let user_count = users.count().get_result::<i64>(conn)?;
assert_eq!(user_count, 1); // 1 条
on_conflict() 指定冲突检测的字段列表:
// 单列冲突:只有 sqlite 和 postgres 支持
use diesel::upsert::*;
diesel::sql_query("CREATE UNIQUE INDEX users_name ON users (name)").execute(conn).unwrap();
let user = User { id: 1, name: "Sean" };
let same_name_different_id = User { id: 2, name: "Sean" };
let same_id_different_name = User { id: 1, name: "Pascal" };
assert_eq!(Ok(1), diesel::insert_into(users).values(&user).execute(conn));
let query = diesel::insert_into(users)
.values(&same_id_different_name)
.on_conflict(id)
.do_nothing()
.execute(conn)?;
// 多列冲突: 只有 sqlite 和 postgres 支持
use diesel::upsert::*;
diesel::sql_query("CREATE UNIQUE INDEX users_name_hair_color ON users (name, hair_color)").execute(conn).unwrap();
let user = User { id: 1, name: "Sean", hair_color: "black" };
let same_name_different_hair_color = User { id: 2, name: "Sean", hair_color: "brown" };
let same_name_same_hair_color = User { id: 3, name: "Sean", hair_color: "black" };
assert_eq!(Ok(1), diesel::insert_into(users).values(&user).execute(conn));
let inserted_row_count = diesel::insert_into(users)
.values(&same_name_different_hair_color)
.on_conflict((name, hair_color))
.do_nothing()
.execute(conn);
assert_eq!(Ok(1), inserted_row_count);
let inserted_row_count = diesel::insert_into(users)
.values(&same_name_same_hair_color)
.on_conflict((name, hair_color))
.do_nothing()
.execute(conn);
assert_eq!(Ok(0), inserted_row_count);
#[cfg(feature = "mysql")]
fn main() {}
// 对于 mysql,只支持所有 KEY 的冲突,而不能指定具体的 KEY
use diesel::upsert::*;
diesel::sql_query("CREATE UNIQUE INDEX users_name ON users (name)").execute(conn).unwrap();
let user = User { id: 1, name: "Sean" };
let same_name_different_id = User { id: 2, name: "Sean" };
let same_id_different_name = User { id: 1, name: "Pascal" };
assert_eq!(Ok(1), diesel::insert_into(users).values(&user).execute(conn));
let user_names = users.select(name).load::<String>(conn)?;
assert_eq!(user_names, vec![String::from("Sean")]);
let query = diesel::insert_into(users)
.values(&same_id_different_name)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_nothing()
.execute(conn)?;
let user_names = users.select(name).load::<String>(conn)?;
assert_eq!(user_names, vec![String::from("Sean")]);
let idx_conflict_result = diesel::insert_into(users)
.values(&same_name_different_id)
.on_conflict(diesel::dsl::DuplicatedKeys)
.do_nothing()
.execute(conn)?;
let user_names = users.select(name).load::<String>(conn)?;
assert_eq!(user_names, vec![String::from("Sean")]);
#[cfg(not(feature = "mysql"))]
fn run_test() -> diesel::QueryResult<()> {Ok(())}
on_conflict() 方法返回的 IncompleteOnConflict 类型提供了 do_nothing() 和 do_update() 方法:
- do_update() 返回的对象的 set() 方法的参数是
AsChangeset
类型, 可以使用 derive macro 来实现, 这样可以传入自定义 struct 类型。 - Eq/Grouped<Eq<Left,Right>> 和 Tuple 也实现了 AsChangeset;
pub fn do_nothing( self, ) -> InsertStatement<T, OnConflictValues<U, Target, DoNothing<T>>, Op, Ret>
pub fn do_update(self) -> IncompleteDoUpdate<Stmt, Target>
// do_update() 返回对象 IncompleteDoUpdate 提供了 set() 方法,参数是 AsChangeset 类型
pub struct IncompleteDoUpdate<Stmt, Target> { /* private fields */ }
pub fn set<Changes>( self, changes: Changes,) -> InsertStatement
// 示例
let user = User { id: 1, name: "Pascal" };
let user2 = User { id: 1, name: "Sean" };
assert_eq!(Ok(1), diesel::insert_into(users).values(&user).execute(conn));
let insert_count = diesel::insert_into(users)
.values(&user2)
.on_conflict(id)
.do_update()
.set(name.eq("I DONT KNOW ANYMORE")) // 参数是 AsChangeset 类型, Eq 实行了该 trait
.execute(conn);
assert_eq!(Ok(1), insert_count);
assert_eq!(Ok(2), insert_count);
let users_in_db = users.load(conn);
assert_eq!(Ok(vec![(1, "I DONT KNOW ANYMORE".to_string())]), users_in_db);
insert 例子:
// https://github.com/diesel-rs/diesel/blob/2.2.x/examples/mysql/all_about_inserts/src/lib.rs
// 创建 table 时,如果为指定 NOT NULL,则默认可以为 NULL
// CREATE TABLE users (
// id INTEGER PRIMARY KEY AUTO_INCREMENT,
// name TEXT NOT NULL,
// hair_color TEXT,
// created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
// updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
// );
// table!() 根据 SQL table 来创建,各字段默认是 NOT NULL,但是 Nullable<T> 是可以为 NULL
mod schema {
diesel::table! {
users {
id -> Integer,
name -> Text,
hair_color -> Nullable<Text>, // 可以插入 NULL
created_at -> Timestamp,
updated_at -> Timestamp,
}
}
}
use schema::users;
// Insertable 是可以插入到 table 的结构体,可以只是部分 table 字段。
#[derive(Deserialize, Insertable)]
#[diesel(table_name = users)]
pub struct UserForm<'a> {
name: &'a str,
hair_color: Option<&'a str>, // Option<T> 表示对应的 table 字段可以是 NULL
}
#[derive(Queryable, PartialEq, Debug)]
struct User {
id: i32,
name: String,
hair_color: Option<String>,
created_at: NaiveDateTime,
updated_at: NaiveDateTime,
}
// 插入缺省值
pub fn insert_default_values(conn: &mut MysqlConnection) -> QueryResult<usize> {
use schema::users::dsl::*;
insert_into(users).default_values().execute(conn)
}
#[test]
fn examine_sql_from_insert_default_values() {
use schema::users::dsl::*;
let query = insert_into(users).default_values();
let sql = "INSERT INTO `users` () VALUES () -- binds: []";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
// 插入单列
pub fn insert_single_column(conn: &mut MysqlConnection) -> QueryResult<usize> {
use schema::users::dsl::*;
insert_into(users).values(name.eq("Sean")).execute(conn)
}
#[test]
fn examine_sql_from_insert_single_column() {
use schema::users::dsl::*;
let query = insert_into(users).values(name.eq("Sean"));
let sql = "INSERT INTO `users` (`name`) VALUES (?) \
-- binds: [\"Sean\"]";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
// 插入多列,多列值用 tuple 来表示
pub fn insert_multiple_columns(conn: &mut MysqlConnection) -> QueryResult<usize> {
use schema::users::dsl::*;
insert_into(users)
.values((name.eq("Tess"), hair_color.eq("Brown")))
.execute(conn)
}
#[test]
fn examine_sql_from_insert_multiple_columns() {
use schema::users::dsl::*;
let query = insert_into(users).values((name.eq("Tess"), hair_color.eq("Brown")));
let sql = "INSERT INTO `users` (`name`, `hair_color`) VALUES (?, ?) \
-- binds: [\"Tess\", \"Brown\"]";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
// 插入 Insertable struct 结构体值,只会插入结构体中定义的字段
pub fn insert_insertable_struct(conn: &mut MysqlConnection) -> Result<(), Box<dyn Error>> {
use schema::users::dsl::*;
let json = r#"{ "name": "Sean", "hair_color": "Black" }"#;
let user_form = serde_json::from_str::<UserForm>(json)?;
insert_into(users).values(&user_form).execute(conn)?;
Ok(())
}
#[test]
fn examine_sql_from_insertable_struct() {
use schema::users::dsl::*;
let json = r#"{ "name": "Sean", "hair_color": "Black" }"#;
let user_form = serde_json::from_str::<UserForm>(json).unwrap();
let query = insert_into(users).values(&user_form);
let sql = "INSERT INTO `users` (`name`, `hair_color`) VALUES (?, ?) \
-- binds: [\"Sean\", \"Black\"]";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
// 对于可以为 NULL 的字段,需要为 struct 对应 Option 字段传入 Option::None 值,这时 SQL 语句的
// 对应值为 DEFAULT。
pub fn insert_insertable_struct_option(conn: &mut MysqlConnection) -> Result<(), Box<dyn Error>> {
use schema::users::dsl::*;
let json = r#"{ "name": "Ruby", "hair_color": null }"#;
let user_form = serde_json::from_str::<UserForm>(json)?;
insert_into(users).values(&user_form).execute(conn)?;
Ok(())
}
#[test]
fn examine_sql_from_insertable_struct_option() {
use schema::users::dsl::*;
let json = r#"{ "name": "Ruby", "hair_color": null }"#;
let user_form = serde_json::from_str::<UserForm>(json).unwrap();
let query = insert_into(users).values(&user_form);
let sql = "INSERT INTO `users` (`name`, `hair_color`) VALUES (?, DEFAULT) \
-- binds: [\"Ruby\"]";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
// 批量插入值:传入 Vec,每个元素为一行,元素类型可以是 single field 或 tuple
pub fn insert_single_column_batch(conn: &mut MysqlConnection) -> QueryResult<usize> {
use schema::users::dsl::*;
insert_into(users)
.values(&vec![name.eq("Sean"), name.eq("Tess")])
.execute(conn)
}
#[test]
fn examine_sql_from_insert_single_column_batch() {
use schema::users::dsl::*;
let values = vec![name.eq("Sean"), name.eq("Tess")];
let query = insert_into(users).values(&values);
let sql = "INSERT INTO `users` (`name`) VALUES (?), (?) \
-- binds: [\"Sean\", \"Tess\"]";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
// 批量插入值:传入 Vec,但是使用 None 来插入缺省值
pub fn insert_single_column_batch_with_default(conn: &mut MysqlConnection) -> QueryResult<usize> {
use schema::users::dsl::*;
insert_into(users)
.values(&vec![Some(name.eq("Sean")), None])
.execute(conn)
}
#[test]
fn examine_sql_from_insert_single_column_batch_with_default() {
use schema::users::dsl::*;
let values = vec![Some(name.eq("Sean")), None];
let query = insert_into(users).values(&values);
let sql = "INSERT INTO `users` (`name`) VALUES (?), (DEFAULT) \
-- binds: [\"Sean\"]";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
// 批量插入值:传入 Vec,每个元素为一行,元素类型为 tuple,实现设置多列的匹配插入
pub fn insert_tuple_batch(conn: &mut MysqlConnection) -> QueryResult<usize> {
use schema::users::dsl::*;
insert_into(users)
.values(&vec![
(name.eq("Sean"), hair_color.eq("Black")),
(name.eq("Tess"), hair_color.eq("Brown")),
])
.execute(conn)
}
#[test]
fn examine_sql_from_insert_tuple_batch() {
use schema::users::dsl::*;
let values = vec![
(name.eq("Sean"), hair_color.eq("Black")),
(name.eq("Tess"), hair_color.eq("Brown")),
];
let query = insert_into(users).values(&values);
let sql = "INSERT INTO `users` (`name`, `hair_color`) \
VALUES (?, ?), (?, ?) \
-- binds: [\"Sean\", \"Black\", \"Tess\", \"Brown\"]";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
// 插入多行:使用 None 来为对应列指定缺省值
pub fn insert_tuple_batch_with_default(conn: &mut MysqlConnection) -> QueryResult<usize> {
use schema::users::dsl::*;
insert_into(users)
.values(&vec![
(name.eq("Sean"), Some(hair_color.eq("Black"))),
(name.eq("Ruby"), None),
])
.execute(conn)
}
#[test]
fn examine_sql_from_insert_tuple_batch_with_default() {
use schema::users::dsl::*;
let values = vec![
(name.eq("Sean"), Some(hair_color.eq("Black"))),
(name.eq("Ruby"), None),
];
let query = insert_into(users).values(&values);
let sql = "INSERT INTO `users` (`name`, `hair_color`) \
VALUES (?, ?), (?, DEFAULT) \
-- binds: [\"Sean\", \"Black\", \"Ruby\"]";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
// 插入多行:使用 struct
pub fn insert_insertable_struct_batch(conn: &mut MysqlConnection) -> Result<(), Box<dyn Error>> {
use schema::users::dsl::*;
let json = r#"[
{ "name": "Sean", "hair_color": "Black" },
{ "name": "Tess", "hair_color": "Brown" }
]"#;
let user_form = serde_json::from_str::<Vec<UserForm>>(json)?;
insert_into(users).values(&user_form).execute(conn)?;
Ok(())
}
#[test]
fn examine_sql_from_insertable_struct_batch() {
use schema::users::dsl::*;
let json = r#"[
{ "name": "Sean", "hair_color": "Black" },
{ "name": "Tess", "hair_color": "Brown" }
]"#;
let user_form = serde_json::from_str::<Vec<UserForm>>(json).unwrap();
let query = insert_into(users).values(&user_form);
let sql = "INSERT INTO `users` (`name`, `hair_color`) \
VALUES (?, ?), (?, ?) \
-- binds: [\"Sean\", \"Black\", \"Tess\", \"Brown\"]";
assert_eq!(sql, debug_query::<Mysql, _>(&query).to_string());
}
#[test]
fn insert_get_results_batch() {
use diesel::result::Error;
let conn = &mut establish_connection();
conn.test_transaction::<_, Error, _>(|conn| {
use diesel::select;
use schema::users::dsl::*;
// 使用 select() 函数,来返回当前时间
let now = select(diesel::dsl::now).get_result::<NaiveDateTime>(conn)?;
let inserted_users = conn.transaction::<_, Error, _>(|conn| {
// 使用 Vec + tuple 来批量插入多行
let inserted_count = insert_into(users)
.values(&vec![
(id.eq(1), name.eq("Sean")),
(id.eq(2), name.eq("Tess")),
])
.execute(conn)?; // 返回受影响的行数量 usize
Ok(users
.order(id.desc())
.limit(inserted_count as i64)
.load(conn)? // 返回后续可以继续链式调用的对象
.into_iter()
.rev()
.collect::<Vec<_>>())
})?;
let expected_users = vec![
User {
id: 1,
name: "Sean".into(),
hair_color: None,
created_at: now,
updated_at: now,
},
User {
id: 2,
name: "Tess".into(),
hair_color: None,
created_at: now,
updated_at: now,
},
];
assert_eq!(expected_users, inserted_users);
Ok(())
});
}
#[test]
fn examine_sql_from_insert_get_results_batch() {
use schema::users::dsl::*;
let values = vec![(id.eq(1), name.eq("Sean")), (id.eq(2), name.eq("Tess"))];
let insert_query = insert_into(users).values(&values);
let insert_sql = "INSERT INTO `users` (`id`, `name`) VALUES (?, ?), (?, ?) \
-- binds: [1, \"Sean\", 2, \"Tess\"]";
assert_eq!(
insert_sql,
debug_query::<Mysql, _>(&insert_query).to_string()
);
// 查询语句没有指定 select() 方法时,默认返回所有列
let load_query = users.order(id.desc());
let load_sql = "SELECT `users`.`id`, `users`.`name`, \
`users`.`hair_color`, `users`.`created_at`, \
`users`.`updated_at` \
FROM `users` \
ORDER BY `users`.`id` DESC \
-- binds: []";
assert_eq!(load_sql, debug_query::<Mysql, _>(&load_query).to_string());
}
#[test]
fn insert_get_result() {
use diesel::result::Error;
let conn = &mut establish_connection();
conn.test_transaction::<_, Error, _>(|conn| {
use diesel::select;
use schema::users::dsl::*;
let now = select(diesel::dsl::now).get_result::<NaiveDateTime>(conn)?;
let inserted_user = conn.transaction::<_, Error, _>(|conn| {
insert_into(users)
.values((id.eq(3), name.eq("Ruby")))
.execute(conn)?;
// first() 返回一条记录
users.order(id.desc()).first(conn)
})?;
let expected_user = User {
id: 3,
name: "Ruby".into(),
hair_color: None,
created_at: now,
updated_at: now,
};
assert_eq!(expected_user, inserted_user);
Ok(())
});
}
#[test]
fn examine_sql_from_insert_get_result() {
use schema::users::dsl::*;
let insert_query = insert_into(users).values((id.eq(3), name.eq("Ruby")));
let insert_sql = "INSERT INTO `users` (`id`, `name`) VALUES (?, ?) -- binds: [3, \"Ruby\"]";
assert_eq!(
insert_sql,
debug_query::<Mysql, _>(&insert_query).to_string()
);
let load_query = users.order(id.desc());
let load_sql = "SELECT `users`.`id`, `users`.`name`, \
`users`.`hair_color`, `users`.`created_at`, \
`users`.`updated_at` \
FROM `users` \
ORDER BY `users`.`id` DESC \
-- binds: []";
assert_eq!(load_sql, debug_query::<Mysql, _>(&load_query).to_string());
}
pub fn explicit_returning(conn: &mut MysqlConnection) -> QueryResult<i32> {
use diesel::result::Error;
use schema::users::dsl::*;
conn.transaction::<_, Error, _>(|conn| {
insert_into(users).values(name.eq("Ruby")).execute(conn)?;
// 使用 select() 方法指定返回的字段(默认返回所有字段),值可以是 tuple 或则实现了 Selectable、
// Queryable 的对象的 as_select() 方法。
// 注:对于查询使用 select() 来指定返回字段, 对于更新、插入等使用 returning() 来指定返回字段。
users.select(id).order(id.desc()).first(conn)
})
}
#[test]
fn examine_sql_from_explicit_returning() {
use schema::users::dsl::*;
let insert_query = insert_into(users).values(name.eq("Ruby"));
let insert_sql = "INSERT INTO `users` (`name`) VALUES (?) -- binds: [\"Ruby\"]";
assert_eq!(
insert_sql,
debug_query::<Mysql, _>(&insert_query).to_string()
);
let load_query = users.select(id).order(id.desc());
let load_sql = "SELECT `users`.`id` FROM `users` ORDER BY `users`.`id` DESC -- binds: []";
assert_eq!(load_sql, debug_query::<Mysql, _>(&load_query).to_string());
}
#[cfg(test)]
fn establish_connection() -> MysqlConnection {
let url = ::std::env::var("DATABASE_URL").unwrap();
MysqlConnection::establish(&url).unwrap()
}
// https://github.com/diesel-rs/diesel/blob/2.2.x/examples/postgres/getting_started_step_2/src/models.rs
#[derive(Queryable, Selectable)]
#[diesel(table_name = posts)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Post {
pub id: i32,
pub title: String,
pub body: String,
pub published: bool,
}
#[derive(Insertable)]
#[diesel(table_name = posts)]
pub struct NewPost<'a> {
pub title: &'a str,
pub body: &'a str,
}
// insert/update 等非 select 语句, 使用 returning() 来返回 struct
diesel::insert_into(posts::table)
.values(&new_post) // new_post 对应类型需要实现 Insertable
.returning(Post::as_returning())
.get_result(conn)
.expect("Error saving new post")
17 update #
diesel::update() 或 diesel::dsl::update(): 传入的是 IntoUpdateTarget 类型,
Identifiable/table/SelectStatement
实现了该 trait(参考 delete 部分)
// diesel::update() 或 diesel::dsl::update() 函数:
pub fn update<T: IntoUpdateTarget>( source: T,) -> UpdateStatement<T::Table, T::WhereClause>
// update() 函数返回的 UpdateStatement 实现了 set/filter/returning() 方法
pub fn set<V>(self, values: V) -> UpdateStatement<T, U, V::Changeset>
where
T: Table,
V: AsChangeset<Target = T>, // 传入的 values 要实现 AsChangeset,一般是 Eq/Grouped 或自定义 struct
UpdateStatement<T, U, V::Changeset>: AsQuery
pub fn filter<Predicate>(self, predicate: Predicate) -> Filter<Self, Predicate>
where Self: FilterDsl<Predicate>,impl<T: QuerySource, U, V, Ret> UpdateStatement<T, U, V, Ret>
pub fn returning<E>(self, returns: E,) -> UpdateStatement<T, U, V, ReturningClause<E>>
where
T: Table,
UpdateStatement<T, U, V, ReturningClause<E>>: Query,
set() 的参数是 Trait diesel::query_builder::AsChangeset
类型:
- 可以被 derived macro 生成,这样可以使用自定义 struct 来作为 set() 参数;
- Eq 和 Grouped 实现了 AsChangeset;
- 如果要设置多个值,可以使用 tuple。
pub trait AsChangeset {
type Target: QuerySource;
type Changeset;
// Required method
fn as_changeset(self) -> Self::Changeset;
}
impl<T: AsChangeset> AsChangeset for Option<T>
// https://docs.diesel.rs/2.2.x/src/diesel/query_builder/update_statement/changeset.rs.html#41
//
// Eq 和 Grouped 也实现了 AsChangeset
impl<Left, Right> AsChangeset for Eq<Left, Right>
impl<Left, Right> AsChangeset for Grouped<Eq<Left, Right>>
示例:
// table users 实现了 IntoUpdateTarget
let updated_rows = diesel::update(users)
.set(name.eq("Jim")) // Eq 实现了 AsChangeset
.filter(name.eq("Sean")) // 限制 update 的范围
.execute(connection);
assert_eq!(Ok(1), updated_rows);
// SelectStatement 也实现了 IntoUpdateTarget, 而且可以限制 update 范围
let updated_name = diesel::update(users.filter(id.eq(1)))
.set(name.eq("Dean")) // Eq 实现了 AsChangeset
.returning(name)
.get_result(connection);
assert_eq!(Ok("Dean".to_string()), updated_name);
let post = diesel::update(posts.find(id))
.set(published.eq(true))
.returning(Post::as_returning())
.get_result(connection)
.unwrap();
println!("Published post {}", post.title);
// Identifiable struct 也实现了 IntoUpdateTarget(可以作为 updat() 的参数),
// AsChangeset struct 可以作为 set() 方法的参数。
#[derive(Queryable, Identifiable, AsChangeset)]
#[diesel(table_name = posts)]
pub struct Post {
pub id: i64,
pub title: String,
pub body: String,
pub draft: bool,
pub publish_at: SystemTime,
pub visit_count: i32,
}
// post 是上面的实现了 Identifiable 的 Post 类型值, 必须要设置 id 字段。
// post 可以同时作为 upadate() 函数和 set() 方法的参数:
diesel::update(post)
.set(posts::draft.eq(false))
.execute(conn)
// post 是上面的实现了 Identifiable 的 Post 类型值, 必须要设置 id 字段
update(post)
// 等效于
update(posts.find(post.id))
// 或
update(posts.filter(id.eq(post.id)))
// 更新单个字段
diesel::update(posts)
.set(visit_count.eq(visit_count + 1))
.execute(conn)
// 更新单个记录的多个字段值, 可以使用 tuple
diesel::update(posts)
.set((
title.eq("[REDACTED]"),
body.eq("This post has been classified"),
))
.execute(conn)
// 通过实现 AsChangetset, 更新多个字段时, 可以传入 post struct 值类型
diesel::update(posts::table).set(post).execute(conn)
#[derive(AsChangeset)]
#[diesel(table_name = posts)]
// #[diesel(treat_none_as_null = true)] // 将 None 设置为 NULL
struct PostForm<'a> {
title: Option<&'a str>, // 可选字段
body: Option<&'a str>,
}
diesel::update(posts::table)
.set(&PostForm {
title: None, // None 表示不设置该字段
body: Some("My new post"),
})
.execute(conn)
// 等效于: UPDATE "posts" SET "body" = $1 -- binds: ["My new post"]
另一个例子:
// https://github.com/diesel-rs/diesel/blob/2.2.x/examples/postgres/all_about_updates/src/lib.rs
use std::time::SystemTime;
#[cfg(test)]
use diesel::debug_query;
#[cfg(test)]
use diesel::pg::Pg;
use diesel::prelude::*;
table! {
posts {
id -> BigInt,
title -> Text,
body -> Text,
draft -> Bool,
publish_at -> Timestamp,
visit_count -> Integer,
}
}
// 实现了 Identifiable 后,可以作为 update() 的参数值(必须指定主键 id 字段)
#[derive(Queryable, Identifiable, AsChangeset)]
pub struct Post {
pub id: i64,
pub title: String,
pub body: String,
pub draft: bool,
pub publish_at: SystemTime,
pub visit_count: i32,
}
// 实现 AsChangeset 的 struct 可以作为 set() 的参数值
#[derive(AsChangeset)]
#[diesel(table_name = posts)]
struct PostForm<'a> {
title: Option<&'a str>,
body: Option<&'a str>,
}
pub fn publish_all_posts(conn: &mut PgConnection) -> QueryResult<usize> {
use crate::posts::dsl::*;
// posts 是 table,可以作为 update() 参数值
// Eq/Grouped 实现了 Aschangeset,可以作为 set() 的参数值
diesel::update(posts).set(draft.eq(false)).execute(conn)
}
#[test]
fn examine_sql_from_publish_all_posts() {
use crate::posts::dsl::*;
assert_eq!(
"UPDATE \"posts\" SET \"draft\" = $1 -- binds: [false]",
debug_query(&diesel::update(posts).set(draft.eq(false))).to_string()
);
}
// filter() 方法对更新范围做限制,
// 也可以通过给 update() 传入 table select 来限制范围
pub fn publish_pending_posts(conn: &mut PgConnection) -> QueryResult<usize> {
use crate::posts::dsl::*;
use diesel::dsl::now;
diesel::update(posts)
.filter(publish_at.lt(now))
.set(draft.eq(false))
.execute(conn)
}
#[test]
fn examine_sql_from_publish_pending_posts() {
use crate::posts::dsl::*;
use diesel::dsl::now;
let query = diesel::update(posts)
.filter(publish_at.lt(now))
.set(draft.eq(false));
assert_eq!(
"UPDATE \"posts\" SET \"draft\" = $1 \
WHERE (\"posts\".\"publish_at\" < CURRENT_TIMESTAMP) \
-- binds: [false]",
debug_query(&query).to_string()
);
}
// post 是实现 Identifiable 的 Post 类型,可以作为 update() 参数,且必须指定主键 id 字段
pub fn publish_post(post: &Post, conn: &mut PgConnection) -> QueryResult<usize> {
diesel::update(post)
.set(posts::draft.eq(false))
.execute(conn)
}
#[test]
fn examine_sql_from_publish_post() {
let post = Post {
id: 1,
title: "".into(),
body: "".into(),
draft: false,
publish_at: SystemTime::now(),
visit_count: 0,
};
// 可见,更新过滤时只使用了 post.id 字段值。
assert_eq!(
"UPDATE \"posts\" SET \"draft\" = $1 WHERE (\"posts\".\"id\" = $2) \
-- binds: [false, 1]",
debug_query(&diesel::update(&post).set(posts::draft.eq(false))).to_string()
);
}
pub fn increment_visit_counts(conn: &mut PgConnection) -> QueryResult<usize> {
use crate::posts::dsl::*;
diesel::update(posts)
.set(visit_count.eq(visit_count + 1))
.execute(conn)
}
#[test]
fn examine_sql_from_increment_visit_counts() {
use crate::posts::dsl::*;
assert_eq!(
"UPDATE \"posts\" SET \"visit_count\" = (\"posts\".\"visit_count\" + $1) \
-- binds: [1]",
debug_query::<Pg, _>(&diesel::update(posts).set(visit_count.eq(visit_count + 1)))
.to_string()
);
}
// 一次更新多个字段值:使用 tuple
pub fn hide_everything(conn: &mut PgConnection) -> QueryResult<usize> {
use crate::posts::dsl::*;
diesel::update(posts)
.set((
title.eq("[REDACTED]"),
body.eq("This post has been classified"),
))
.execute(conn)
}
#[test]
fn examine_sql_from_hide_everything() {
use crate::posts::dsl::*;
let query = diesel::update(posts).set((
title.eq("[REDACTED]"),
body.eq("This post has been classified"),
));
assert_eq!(
"UPDATE \"posts\" SET \"title\" = $1, \"body\" = $2 \
-- binds: [\"[REDACTED]\", \"This post has been classified\"]",
debug_query::<Pg, _>(&query).to_string()
);
}
// Post 实现了 AsChangeset,故可以作为 set() 方法的参数(忽略主键 id)
pub fn update_from_post_fields(post: &Post, conn: &mut PgConnection) -> QueryResult<usize> {
diesel::update(posts::table).set(post).execute(conn)
}
// 可见 update 时忽略设置传入的 post.id
#[test]
fn examine_sql_from_update_post_fields() {
let now = SystemTime::now();
let post = Post {
id: 1,
title: "".into(),
body: "".into(),
draft: false,
publish_at: now,
visit_count: 0,
};
let sql = format!(
"UPDATE \"posts\" SET \
\"title\" = $1, \
\"body\" = $2, \
\"draft\" = $3, \
\"publish_at\" = $4, \
\"visit_count\" = $5 \
-- binds: [\
\"\", \
\"\", \
false, \
{now:?}, \
0\
]"
);
assert_eq!(
sql,
debug_query(&diesel::update(posts::table).set(&post)).to_string()
);
}
// PostForm 实现了 AsChangeset,可以作为 set() 的参数:
pub fn update_with_option(conn: &mut PgConnection) -> QueryResult<usize> {
diesel::update(posts::table)
.set(&PostForm {
title: None,
body: Some("My new post"),
})
.execute(conn)
}
#[test]
fn examine_sql_from_update_with_option() {
#[derive(AsChangeset)]
#[diesel(table_name = posts)]
struct PostForm<'a> {
title: Option<&'a str>,
body: Option<&'a str>,
}
let post_form = PostForm {
title: None,
body: Some("My new post"),
};
let query = diesel::update(posts::table).set(&post_form);
assert_eq!(
"UPDATE \"posts\" SET \"body\" = $1 \
-- binds: [\"My new post\"]",
debug_query::<Pg, _>(&query).to_string()
);
}
update:
- 对于 delete/update, 除了可以使用 table field 来过滤外, 还可以使用实现了 Identifiable 的 struct来实现 delete 和 update.
let post = connection
.transaction(|connection| {
// first() 没有跟 optional() 表示肯定可以查到一个匹配的 id 记录
// ? 表示查询出错或查不到 id 记录
let post = posts.find(id).select(Post::as_select()).first(connection)?;
// update 使用 posts.find() 来限制更新范围
diesel::update(posts.find(id))
.set(published.eq(true))
.execute(connection)?;
Ok(post)
})
.unwrap_or_else(|_: diesel::result::Error| panic!("Unable to find post {}", id));
println!("Published post {}", post.title);
18 sql_query #
diesel::sql_query() 或 diesel::dsl::sql_query() 使用 raw SQL 来构造完整的查询:
- 使用 SqlQuery::bind() 来设置示例参数.
pub fn sql_query<T: Into<String>>(query: T) -> SqlQuery
// 返回的 SqlQuery 对象实现了 bind()/sql() 方法:
pub fn bind<ST, Value>(self, value: Value) -> UncheckedBind<Self, Value, ST>
// Appends a piece of SQL code at the end.
pub fn sql<T: AsRef<str>>(self, sql: T) -> Self
// 示例
let users = sql_query("SELECT * FROM users ORDER BY id").load(connection);
let expected_users = vec![
User { id: 1, name: "Sean".into() },
User { id: 2, name: "Tess".into() },
];
assert_eq!(Ok(expected_users), users);
let users = sql_query("SELECT * FROM users WHERE id > ? AND name <> ?");
let users = users
.bind::<Integer, _>(1) // 绑定两个位置参数
.bind::<Text, _>("Tess")
.get_results(connection);
let expected_users = vec![
User { id: 3, name: "Jim".into() },
];
assert_eq!(Ok(expected_users), users);
// sql_query 查询的结果也可以反序列化到 struct model
let complex_result = diesel::sql_query("
SELECT u.*, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id
HAVING COUNT(p.id) > 5
")
.load::<ComplexUserResult>(conn)?;
如果部分使用 raw SQL 则可以用 sql():
// https://github.com/atanmarko/rust-diesel-examples/blob/main/src/main.rs
// Get average mark of every student
// select s.first_name as student_name, s.last_name as student_surname,
// avg(g.grade) from public.students as s, public.grades as g
// where s.id=g.student
// group by s.first_name, s.last_name;
//
// group_by support is missing in Diesel 1.x https://github.com/diesel-rs/diesel/issues/210
let result = students
.inner_join(grades)
.select((
schema::students::columns::first_name,
schema::students::columns::last_name,
sql::<Double>("avg(grades.grade) AS grade"),
))
.filter(diesel::dsl::sql( "true group by students.first_name, students.last_name", ))
.load::<(String, String, f64)>(&conn)?;
println!("Average student mark: \n {:#?}\n\n", result);
// Use raw sql in queries
let result = students
.select((
sql::<Uuid>("id as Identification"),
sql::<Text>("first_name as Name"),
sql::<Int8>("age*2 as DoubleAge"),
))
.load::<(uuid::Uuid, String, i64)>(&conn)?;
println!("Custom sql:\n {:#?}\n\n", result);
19 define_sql_function! #
https://docs.diesel.rs/master/diesel/prelude/macro.define_sql_function.html
https://diesel.rs/guides/composing-applications.html
Declare a sql function for use in your code.
Diesel only provides support for a very small number of SQL functions. This macro enables you to
add additional functions from the SQL standard, as well as any custom functions
your application
might have.
The syntax for this macro is very similar to that of a normal Rust function, except the argument
and return types will be the SQL types
being used. Typically, these types will come from
diesel::sql_types
This macro will generate two items. A function with the name that you’ve given, and a module with a helper type representing the return type of your function. For example, this invocation:
define_sql_function!(fn lower(x: Text) -> Text);
will generate this code:
// 同名函数
pub fn lower<X>(x: X) -> lower<X> {
...
}
// 函数返回值类型
pub type lower<X> = ...;
Most attributes given to this macro will be put on the generated function (including doc comments).
Adding Doc Comments
use diesel::sql_types::Text;
// 函数输入输出类型来源于 diesel::sql_types
define_sql_function! {
/// Represents the `canon_crate_name` SQL function, created in
/// migration ....
fn canon_crate_name(a: Text) -> Text;
}
let target_name = "diesel";
crates.filter(canon_crate_name(name).eq(canon_crate_name(target_name)));
// This will generate the following SQL
// SELECT * FROM crates WHERE canon_crate_name(crates.name) = canon_crate_name($1)
Special Attributes
There are a handful of special attributes that Diesel will recognize. They are:
-
#[aggregate]
Indicates that this is an aggregate function, and that NonAggregate shouldn’t be implemented.
-
#[sql_name = “name”]
The SQL to be generated is different from the Rust name of the function. This can be used to represent functions which can take many argument types, or to capitalize function names.
Functions can also be generic. Take the definition of sum, for example:
use diesel::sql_types::Foldable;
define_sql_function! {
#[aggregate]
#[sql_name = "SUM"]
fn sum<ST: Foldable>(expr: ST) -> ST::Sum;
}
crates.select(sum(id));
SQL Functions without Arguments
A common example is ordering a query using the RANDOM() sql function, which can be implemented using define_sql_function! like this:
define_sql_function!(fn random() -> Text);
crates.order(random());
Use with SQLite
On most backends, the implementation of the function is defined in a migration using CREATE FUNCTION
. On SQLite, the function is implemented in Rust instead. You must call register_impl
or
register_nondeterministic_impl
(in the generated function’s _utils
module) with every connection
before you can use the function.
These functions will only be generated if the sqlite feature
is enabled, and the function is not
generic. SQLite doesn’t support generic functions and variadic functions.
use diesel::sql_types::{Integer, Double};
define_sql_function!(fn add_mul(x: Integer, y: Integer, z: Double) -> Double);
let connection = &mut SqliteConnection::establish(":memory:")?;
add_mul_utils::register_impl(connection, |x: i32, y: i32, z: f64| {
(x + y) as f64 * z
})?;
let result = select(add_mul(1, 2, 1.5)).get_result::<f64>(connection)?;
assert_eq!(4.5, result);
Panics
If an implementation of the custom function panics and unwinding is enabled, the panic is caught and the function returns to libsqlite with an error. It can’t propagate the panics due to the FFI boundary.
This is the same for custom aggregate functions.
Custom Aggregate Functions
Custom aggregate functions can be created in SQLite by adding an #[aggregate]
attribute inside
define_sql_function
. register_impl
(in the generated function’s _utils module) needs to be called
with a type implementing the SqliteAggregateFunction
trait as a type parameter as shown in the
examples below.
use diesel::sql_types::Integer;
use diesel::sqlite::SqliteAggregateFunction;
define_sql_function! {
#[aggregate]
fn my_sum(x: Integer) -> Integer;
}
#[derive(Default)]
struct MySum { sum: i32 }
impl SqliteAggregateFunction<i32> for MySum {
type Output = i32;
fn step(&mut self, expr: i32) {
self.sum += expr;
}
fn finalize(aggregator: Option<Self>) -> Self::Output {
aggregator.map(|a| a.sum).unwrap_or_default()
}
}
fn run() -> Result<(), Box<dyn (::std::error::Error)>> {
let connection = &mut SqliteConnection::establish(":memory:")?;
my_sum_utils::register_impl::<MySum, _>(connection)?;
let total_score = players.select(my_sum(score))
.get_result::<i32>(connection)?;
println!("The total score of all the players is: {}", total_score);
Ok(())
}
With multiple function arguments, the arguments are passed as a tuple
to SqliteAggregateFunction
use diesel::sql_types::{Float, Nullable};
use diesel::sqlite::SqliteAggregateFunction;
define_sql_function! {
#[aggregate]
fn range_max(x0: Float, x1: Float) -> Nullable<Float>;
}
#[derive(Default)]
struct RangeMax<T> { max_value: Option<T> }
impl<T: Default + PartialOrd + Copy + Clone> SqliteAggregateFunction<(T, T)> for RangeMax<T> {
type Output = Option<T>;
fn step(&mut self, (x0, x1): (T, T)) {
// Compare self.max_value to x0 and x1
}
fn finalize(aggregator: Option<Self>) -> Self::Output {
aggregator?.max_value
}
}
fn run() -> Result<(), Box<dyn (::std::error::Error)>> {
let connection = &mut SqliteConnection::establish(":memory:")?;
range_max_utils::register_impl::<RangeMax<f32>, _, _>(connection)?;
let result = student_avgs.select(range_max(s1_avg, s2_avg))
.get_result::<Option<f32>>(connection)?;
if let Some(max_semester_avg) = result {
println!("The largest semester average is: {}", max_semester_avg);
}
Ok(())
}
20 r2d2 连接池 #
use diesel::pg::PgConnection;
use diesel::r2d2::{self, ConnectionManager};
type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
let manager = ConnectionManager::<PgConnection>::new(DATABASE_URL);
let pool = Pool::builder()
.max_size(15)
.build(manager)
.expect("Failed to create pool.");
21 事务 #
conn.transaction(|conn| {
diesel::insert_into(users)
.values(&new_user)
.execute(conn)?;
diesel::insert_into(posts)
.values(&new_post)
.execute(conn)
})
22 异步 #
参考:Diesel异步编程: 深入理解 Rust ORM 的异步特性
1 设置项目
首先,在`Cargo.toml`中添加必要的依赖:
[dependencies]
diesel = { version = "2.1.0", features = ["postgres"] }
diesel-async = { version = "0.3.1", features = ["postgres"] }
tokio = { version = "1.0", features = ["full"] }
2 建立异步连接
使用`AsyncConnection` trait 来建立异步连接:
use diesel_async::{AsyncPgConnection, AsyncConnection};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let conn = AsyncPgConnection::establish("postgres://username:password@localhost/diesel_demo").await?;
// 使用连接...
Ok(())
}
3 执行异步查询
使用`RunQueryDsl` trait的异步方法来执行查询:
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
#[derive(Queryable)]
struct User {
id: i32,
name: String,
}
#[tokio::main]
async fn main() -> QueryResult<()> {
let mut conn = AsyncPgConnection::establish("...").await?;
let users = users::table
.limit(5)
.load::<User>(&mut conn)
.await?;
for user in users {
println!("User: {}", user.name);
}
Ok(())
}
高级特性
1 异步连接池
使用`deadpool-diesel` 来管理异步连接池:
use deadpool_diesel::{Manager, Pool};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let manager = Manager::new("postgres://username:password@localhost/diesel_demo");
let pool = Pool::builder(manager).max_size(16).build()?;
let conn = pool.get().await?;
// 使用连接...
Ok(())
}
2 异步事务
使用`Connection::transaction`方法来执行异步事务:
use diesel_async::AsyncConnection;
async fn transfer_funds(
conn: &mut AsyncPgConnection,
from: i32,
to: i32,
amount: f64,
) -> QueryResult<()> {
conn.transaction(|conn| {
Box::pin(async move {
diesel::update(accounts.find(from))
.set(balance.eq(balance - amount))
.execute(conn)
.await?;
diesel::update(accounts.find(to))
.set(balance.eq(balance + amount))
.execute(conn)
.await?;
Ok(())
})
})
.await
}
3 异步迁移
使用`AsyncMigrations`来执行异步数据库迁移:
use diesel_async::AsyncConnection;
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
async fn run_migrations(conn: &mut AsyncPgConnection) -> Result<(), Box<dyn std::error::Error>> {
conn.run_pending_migrations(MIGRATIONS).await?;
Ok(())
}
4 组合异步操作
使用Rust的`async`/`await`语法来组合多个异步操作:
async fn complex_operation(pool: &Pool) -> QueryResult<Vec<ComplexResult>> {
let mut conn = pool.get().await?;
let users = users::table.load::<User>(&mut conn).await?;
let posts = Post::belonging_to(&users)
.load::<Post>(&mut conn)
.await?;
let user_posts = users.into_iter().zip(posts.grouped_by(&users)).collect::<Vec<_>>();
// 进行一些复杂的处理...
Ok(complex_results)
}
性能考虑
1 并发查询
利用`futures`crate来并发执行多个查询:
use futures::future::try_join_all;
async fn fetch_user_data(pool: &Pool, user_ids: Vec<i32>) -> QueryResult<Vec<UserData>> {
let futures = user_ids.into_iter().map(|id| {
let pool = pool.clone();
tokio::spawn(async move {
let mut conn = pool.get().await?;
users::table.find(id).first::<UserData>(&mut conn).await
})
});
let results = try_join_all(futures).await?;
results.into_iter().collect()
}
2 批量操作
使用批量插入来提高性能:
use diesel::pg::upsert::on_constraint;
async fn bulk_insert_users(conn: &mut AsyncPgConnection, new_users: Vec<NewUser>) -> QueryResult<()> {
diesel::insert_into(users::table)
.values(&new_users)
.on_conflict(on_constraint("users_pkey"))
.do_update()
.set(name.eq(excluded(name)))
.execute(conn)
.await?;
Ok(())
}
最佳实践
-
连接池: 总是使用连接池来管理数据库连接,避免频繁建立和关闭连接。
-
错误处理: 使用`?`操作符或`match`语句来正确处理异步操作中的错误。
-
超时处理: 为长时间运行的查询设置超时,以防止资源耗尽:
use tokio::time::timeout; use std::time::Duration; async fn query_with_timeout<T>( conn: &mut AsyncPgConnection, query: impl RunQueryDsl<PgAsyncConnection> + 'static, ) -> Result<T, Box<dyn std::error::Error>> where T: 'static, { timeout(Duration::from_secs(5), query.get_result(conn)).await??; Ok(()) }
-
避免过度使用`async`: 不是所有操作都需要异步。对于简单的CRUD操作,同步版本可能更简单且性能足够。
-
正确使用事务: 对于需要保证一致性的复杂操作,使用事务来确保原子性。
-
测试: 为异步代码编写单元测试和集成测试,确保异步操作的正确性:
#[tokio::test] async fn test_user_creation() { let mut conn = establish_connection().await.unwrap(); let user = create_user(&mut conn, "Alice").await.unwrap(); assert_eq!(user.name, "Alice"); }
结论
Diesel的异步支持为Rust开发者提供了一种高效、类型安全的方式来在异步环境中进行数据库操作。通过利用 Rust的异步特性和Diesel强大的查询DSL,开发者可以构建出既高性能又易于维护的数据库应用程序。
从基本的异步查询到复杂的事务和批量操作,Diesel的异步API提供了丰富的工具来处理各种数据库操作场景。通过遵循最佳实践并充分利用Rust的异步生态系统,开发者可以充分发挥Diesel异步特性的潜力,构建出色的异步数据库应用程序。
随着Rust异步生态系统的不断发展,我们可以期待Diesel的异步支持会变得更加强大和易用。持续关注Diesel和相关crates的更新,将有助于在实际项目中更好地运用这些异步特性。
23 实践 #
- 查询场景:定义一个 struct model,它实现了 Queryable, Selectable trait,可以用于 select() 和
returning() 和 RunQueryDsl 的返回值类型:
- 实现了 Queryable 后:可以作为 RunQueryDsl 各方法 get_result/get_results/first() 的返回值类型,如 first::<User>(), 要求Queryable 的 struct 解构和字段顺序必须与 table 完全匹配(具体可以参考 src/schema.rs 中的table!() 宏对表的定义);
- 实现了 Selectable 后: value 可以作为 select(value.as_select()) 或 returning(value.as_returning()) 的参数,用于指定要返回的值类型;
- 插入场景:定义一个 struct model,它实现了 Insertable trait,可以用于 insert 和 update:
// 代表可查询的结构体(Queyable 可以是部分字段,Selectable 是完整字段)
#[derive(Queryable)]
struct User {
id: i32,
name: String,
age: i32,
hair_color: Option<&'a str>, // Option<T> 代表可以是 NULL 的可选字段
}
// 代表可插入的结构体(可以是部分 table 字段)
#[derive(Insertable)]
#[table_name = "users"]
struct NewUser {
name: String,
age: i32,
}
// 插入
let new_user = NewUser {
name: "John Doe",
age: 30,
};
insert_into(users).values(&new_user).execute(&connection)?;
// 查询
let results = users.load<User>(&connection)?;
let results = users.filter(age.gt(21)).load<User>(&connection)?;
// 更新
let target = users.filter(name.eq("John Doe"));
update(target).set(name.eq("Jonathan Doe")).execute(&connection)?;
// 删除
delete(users.filter(name.eq("Jonathan Doe"))).execute(&connection)?;
// 使用 Query builder 来构建复杂查询
let complex_query = users
.filter(name.like("%Doe%"))
.filter(age.between(18, 30))
.order(age.desc())
.limit(5)
.load<User>(&connection)?;
// join 和子查询
let data = users::table
.inner_join(posts::table)
.select((users::name, posts::title))
.filter(posts::published.eq(true))
.load<(&str, &str)>(&connection)?;
// 自定义 sql function
sql_function!(fn lower(x: diesel::sql_types::Text) -> diesel::sql_types::Text);
let lower_case_names = users
.select(lower(users::name))
.load<String>(&connection)?;
// 测试
#[cfg(test)]
mod tests {
use super::*;
use diesel::prelude::*;
use diesel::connection::Connection;
fn establish_connection() -> PgConnection {
let database_url = std::env::var("TEST_DATABASE_URL")
.expect("TEST_DATABASE_URL must be set");
PgConnection::establish(&database_url)
.expect("Error connecting to test database")
}
}
#[test]
fn test_user_creation() {
let conn = establish_connection();
let new_user = NewUser { name: "Test User", age: 25 };
let result = create_user(&conn, &new_user);
assert!(result.is_ok());
}
query:
// https://github.com/diesel-rs/diesel/blob/2.2.x/examples/mysql/getting_started_step_1/src/lib.rs
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::posts)]
#[diesel(check_for_backend(diesel::mysql::Mysql))]
pub struct Post {
pub id: i32,
pub title: String,
pub body: String,
pub published: bool,
}
// results 是一个 Vec<T>,T 类型是 select() 的参数类型 Post
let results = posts
.filter(published.eq(true))
.limit(5)
.select(Post::as_select()) // 使用 Selectable struct 来指定返回字段和结构类型
.load(connection)
.expect("Error loading posts");
println!("Displaying {} posts", results.len());
for post in results {
println!("{}", post.title);
println!("-----------\n");
println!("{}", post.body);
}
posts::table
.order(posts::id.desc()) // 排序
.select(Post::as_select()) // 返回值
.first(conn) // 返回一个对象
let post = posts
.find(post_id) // find 需要传入 PK 值
.select(Post::as_select())
.first(connection) // 返回 Post 类型,由于可能查不到,所以需要进一步调用 optional() 方法
.optional(); // This allows for returning an Option<Post>, otherwise it will throw an error
match post {
Ok(Some(post)) => println!("Post with id: {} has a title: {}", post.id, post.title),
Ok(None) => println!("Unable to find post {}", post_id),
Err(_) => println!("An error occurred while fetching post {}", post_id),
}
24 原理 #
各 DB Backend 需要实现:
- QueryBuilder trait:用于生成提交给数据库的 SQL 语句:
- BindCollector trait:用于为 Connection 提供 bind value/parameters 格式信息。
- 可以使用缺省的 Struct diesel::query_builder::bind_collector::RawBytesBindCollector 实现;
- 每一个 param 都有一个 DB 对应的 TypeMetadata 值,用于指定该 param 的类型;
- RawValue trait: how values are received from the database;是传递给 FromSql 的具体类型;
- SqlDialect trait:数据库相关的 SQL 方言;
- TypeMetadata:Backend 如何识别 types,On PostgreSQL, this is the type’s OID. On MySQL and SQLite, this is an enum representing all storage classes they support.
- HasSqlType:指定支持的 SQL 类型;常见的 SQL 类型:SmallInt Integer BigInt Float Double Text Binary Date Time Timestamp
pub trait Backend
where
Self: Sized + SqlDialect + TypeMetadata + HasSqlType<SmallInt> + HasSqlType<Integer> + HasSqlType<BigInt> + HasSqlType<Float> + HasSqlType<Double> + HasSqlType<Text> + HasSqlType<Binary> + HasSqlType<Date> + HasSqlType<Time> + HasSqlType<Timestamp>,
{
type QueryBuilder: QueryBuilder<Self>;
type RawValue<'a>;
type BindCollector<'a>: BindCollector<'a, Self> + 'a;
}
缩写:
- ST: Diesel 的 sql_type 类型, 如 Int4, Integer 等;
24.1 Selectable 和 Queryable macro #
Queryable: 将 Rust tuple 转为 struct -> FromStaticSqlRow: 将 ST tuple 转为 Rust tuple -> FromSql: 将 ST tuple 中各元素类型转为 Rust 类型值
Selectable: 将 struct model 转为 Expression tuple -> FromSqlRow: 将 Exprssion tuple 中各 Expression 类型转为 Rust 类型Expression -> FromStaticSqlRow -> FromSql 关键:
- diesel 为大量 Rust 类型实现了 FromSql, 即从 DB RawValue 到 Rust 类型的自动转换;
- diesel 为上面 Rust 类型的 tuple 也自动实现了 FromSql trait;
- 而 Selectable struct model 会被转换为 ST tuple, 所以也实现了 FromSql, 即从查询的 DB Row Record 到 struct model 的转换;
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::posts)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Post {
pub id: i32,
pub title: String,
pub body: String,
pub published: bool,
}
diesel::table! {
posts (id) {
id -> Int4,
title -> Varchar,
body -> Text,
published -> Bool,
}
}
Queryable: 使用 (i32, String, String, bool): FromStaticSqlRow<(__ST0, __ST1, __ST2, __ST3), __DB>,
将 table!() 中各字段的 diesel sql_type 类型的 tuple (Row 关联类型)转换为 Rust struct model 类型:
- 所以, Querable 定义的 struct model 的各 field 必须与 table!() 生成的 diesel sql_type 类型字段一一匹配, 类型和字段顺序必须一致.(因为是 tuple 而非 field name 校验);
Queryable 的 build() 方法将传入的 Row tuple 值准换为 struct mode 类型值:
- diesel 从数据库查询到的记录
用 tuple 来表示
, 默认返回所有字段, 可以使用 select()/returning() 方法来指定返回的字段 tuple, 这个 tuple 需要与 Queryable struct model 的各 field 完全一致;
// Queryable 宏展开的结果: 使用 FromStaticSqlRow 将 Row tuple 转换为对应的 Rust struct model 类型
#[allow(unused_imports)]
const _: () = {
use diesel;
use diesel::deserialize::{self, FromStaticSqlRow, Queryable};
use diesel::row::{Field as _, Row as _};
use std::convert::TryInto;
impl<__DB: diesel::backend::Backend, __ST0, __ST1, __ST2, __ST3>
Queryable<(__ST0, __ST1, __ST2, __ST3), __DB> for Post
where (i32, String, String, bool): FromStaticSqlRow<(__ST0, __ST1, __ST2, __ST3), __DB>,
{
// struct model 各 field 值类型 tuple
type Row = (i32, String, String, bool);
// build() 方法将 Row tuple 转换为 struct model 类型值
fn build(row: Self::Row) -> deserialize::Result<Self> {
Ok(Self {
id: row
.0
.try_into()?,
title: row
.1
.try_into()?,
body: row
.2
.try_into()?,
published: row
.3
.try_into()?,
})
}
}
};
Selectable: 使用 FromSqlRow
trait 将 table!() 各 field 的 diesel sql_type 如 Int4 转换为 Model
struct 对应 field 的 Rust 类型 i32;
- Diesel 默认为 Rust 各基础类型如 i32 等实现了 FromSql; 对于自定义类型 T, 需要使用 derive macro 实现
FromSqlRow
trait, 但内部可以使用 i32 的实现; - 对于自定义类型 T, 需要使用 derive macro 实现
FromSqlRow
trait, 然后该字段类型才能用在 Selectable struct model 中. - SelectExpression 是 diesel sql_type 的 tuple, 其中的元素, 如 r#id 是 diesel sql_type 类型, 它实现了
Expression trait
, 它内部封装了 sql filename 和 type, 所以实现 Selectable trait 的 struct model 的 field 可以是table!() 的子集, 及可以是表的部分字段;
Expression: table!() 为 SQL table 各 field struct 类型实现了该 trait, 内部封装了 diesel sql_type 类型, 还可以提供 field_name 信息;
SelectExpression: Expression tuple 实现了该 trait, 用于保存 Selectable struct model 各 field 的 diesel sql type 类型信息;
#[allow(unused_imports)]
const _: () = {
use diesel;
use diesel::expression::Selectable;
impl<__DB: diesel::backend::Backend> Selectable<__DB> for Post {
type SelectExpression = (
crate::schema::posts::r#id, // Int4
crate::schema::posts::r#title, // Varchar
crate::schema::posts::r#body, // Text
crate::schema::posts::r#published, // Bool
);
fn construct_selection() -> Self::SelectExpression {
(
crate::schema::posts::r#id,
crate::schema::posts::r#title,
crate::schema::posts::r#body,
crate::schema::posts::r#published,
)
}
}
fn _check_field_compatibility<__DB: diesel::backend::Backend>()
where
i32: diesel::deserialize::FromSqlRow<
diesel::dsl::SqlTypeOf<crate::schema::posts::r#id>,
diesel::pg::Pg,
>, // 从 Diesel sql type 如 Int4 类型可以转换为 Rust i32 类型
String: diesel::deserialize::FromSqlRow<
diesel::dsl::SqlTypeOf<crate::schema::posts::r#title>,
diesel::pg::Pg,
>,
String: diesel::deserialize::FromSqlRow<
diesel::dsl::SqlTypeOf<crate::schema::posts::r#body>,
diesel::pg::Pg,
>,
bool: diesel::deserialize::FromSqlRow<
diesel::dsl::SqlTypeOf<crate::schema::posts::r#published>,
diesel::pg::Pg,
>,
{
}
};
24.2 FromSqlRow/FromStaticSqlRow/FromSql #
Queryable: 使用 (i32, String, String, bool): FromStaticSqlRow<(__ST0, __ST1, __ST2, __ST3), __DB>,
来校验 table!() 中各字段的 diesel sql_type 类型的 tuple 值 (__ST0, __ST1, __ST2, __ST3) 可以转换为对应的 Rust 类型值 tuple (i32, String, String, bool);
Selectable: 使用 FromSqlRow
trait 将 Diesel sql type 如 Int4 转换为 Rust 类型 i32.
FromSqlRow 的 build_from_row(Row) -> T 方法:
- 内部使用 FromStaticSqlRow 将 Sql Row (从 DB 查询的 row 结果) 转换为 struct model 各 field 对应的 Rust 类型值 tuple;
- 再使用 Queryable 的 build() 方法, 将 tuple 转换为 struct model 类型:
而 FromStaticSqlRow 使用 T: FromSql<ST>
来将 ST 对应的 diesel sql type, 如 (Int4, Bool, Varchar)
向 Rust tuple 类型 T, 如 (i32, bool, String), 的转换.
pub trait RowIndex<I> {
// Required method
fn idx(&self, idx: I) -> Option<usize>;
}
// 封装了 field name 和 value, 其中 value 是 RawValue 类型, 它和 DB Backend 有关, 比如对于 Pg, 它
// 实际是 struct PgValue 类型, 该类型内部使用 &[u8] bytes 保存 value 值.
pub trait Field<'a, DB: Backend> {
// Required methods
fn field_name(&self) -> Option<&str>;
// RawValue 和 DB 有关, 表示从 DB 查询到的值类型, 如 Pg 实际对应的是 PgValue
// pub struct PgValue<'a> {
// raw_value: &'a [u8],
// type_oid_lookup: &'a dyn TypeOidLookup,
// }
fn value(&self) -> Option<DB::RawValue<'_>>;
// Provided method
fn is_null(&self) -> bool { ... }
}
// Row::get_value() 使用 FromSql 将 diesel sql_type ST 类型转换为 Rust T 类型
pub trait Row<'a, DB: Backend>: RowIndex<usize> + for<'b> RowIndex<&'b str> + RowSealed + Sized {
type Field<'f>: Field<'f, DB> where Self: 'f, 'a: 'f;
// Required methods
fn field_count(&self) -> usize;
// get() 返回 Field 类型值, 内部还是 raw bytes
fn get<'b, I>(&'b self, idx: I) -> Option<Self::Field<'b>> where Self: RowIndex<I>, 'a: 'b;
// get_value() 返回 T 类型值, 内部使用 T: FromSql<ST, DB> 将数据库类型值 ST 转换为 T
// 这里的 ST 有可能是 diesel sql_type tuple, 如 (Int4, Bool), 而 T 可能是 struct model.
fn get_value<ST, T, I>(&self, idx: I) -> Result<T> where Self: RowIndex<I>, T: FromSql<ST, DB>
}
// 为 Queryable 实现 FromSqlRow, FromSqlRow 内部调用 FromStaticSqlRow, 将传入的 Row 转换为 T struct model
impl<T, ST, DB> FromSqlRow<ST, DB> for T
where T: Queryable<ST, DB>, ST: SqlTypeOrSelectable, DB: Backend, T::Row: FromStaticSqlRow<ST, DB>,
{
// This inline(always) attribute is here as benchmarks have shown up to 5% reduction in
// instruction count of having it here
//
// A plain inline attribute does not show similar improvements
#[inline(always)]
fn build_from_row<'a>(row: &impl Row<'a, DB>) -> Result<Self> {
// 先将 sql Row 转换为 T struct model 各 field 对应的 Rust 值类型 tuple, 如 (i32, String, bool)
let row = <T::Row as FromStaticSqlRow<ST, DB>>::build_from_row(row)?;
// 再调用 Queryable 的 build() 方法, 将 row tuple 转换为 T 对应的 struct model
T::build(row)
}
}
// 利用 FromSql trait 实现 ST 类型 (Int4, Bool, Varchar) 向 Rust tuple 类型 (i32, bool, String) 的转换.
//
// 将 ST 类型, 如 (Int4, Bool, Varchar) 转换为 T 类型, 这里的 T 可能是 Rust struct model 各 field
// 类型值组成的 tuple 类型, 如 (i32, bool, String).
//
impl<T, ST, DB> FromStaticSqlRow<ST, DB> for T where DB: Backend, T: FromSql<ST, DB>, ST: SingleValue,
{
fn build_from_row<'a>(row: &impl Row<'a, DB>) -> Result<Self> {
use crate::row::Field;
// 先从 Row 获得 Field, Field 提供了 field_name() 和 value() 方法
let field = row.get(0).ok_or(crate::result::UnexpectedEndOfRow)?;
// field.value() 返回 Option<DB::RawValue> 类型值, 然后使用 T 的 FromSql 的
// from_nullable_sql() 方法转换为 T 类型值.
T::from_nullable_sql(field.value()).map_err(|e| {
if e.is::<crate::result::UnexpectedNullError>() {
e
} else {
Box::new(crate::result::DeserializeFieldError::new(field, e))
}
})
}
}
对于查询, 表的每个字段使用 FromSql trait 的 fn from_sql(bytes: DB::RawValue<’_>) -> Result<Self>; 方法将从数据库读取的 raw bytes 值 &[u8] 转换为 Rust 类型 T:
T: FromSql<ST, DB> 提供了将 diesel sql type 组成的 ST 类型, 如 (Int4, Bool, Varchar) 向 Rust tuple 类型 T, 如 (i32, bool, String), 转换的功能.
Diesel 默认为 Rust 各基础类型, 复杂类型以及它们的 tuple 实现了 FromSql:
- https://docs.diesel.rs/2.2.x/diesel/deserialize/trait.FromSql.html
- String/Uuid/Value(Json)/Time/SystemTime/NaiveDateTime/PrimitiveDateTime/DateTime/Vec<u8>/Cow/(x,xx)
FromSql trait 的 fn from_sql(bytes: DB::RawValue<’_>) -> Result<Self>; 方法将输入的 DB::RawValue
转换为自定义类型 Self T:
- 一般使用 diesel 默认为 Rust 各类型实现的 FromSql trait 来实现到自定义类型 T 的转换, 如 i32::from_sql(bytes) (or the explicit form <i32 as FromSql<Integer, DB>>::from_sql(bytes))
/// Deserialize a single field of a given SQL type.
///
/// When possible, implementations of this trait should prefer to use an
/// existing implementation, rather than reading from `bytes`. (For example, if
/// you are implementing this for an enum which is represented as an integer in
/// the database, prefer `i32::from_sql(bytes)` (or the explicit form
/// `<i32 as FromSql<Integer, DB>>::from_sql(bytes)`) over reading from `bytes`
/// directly)
///
/// Types which implement this trait should also have `#[derive(FromSqlRow)]`
///
/// ### Backend specific details
///
/// - For PostgreSQL, the bytes will be sent using the binary protocol, not text.
/// - For SQLite, the actual type of `DB::RawValue` is private API. All
/// implementations of this trait must be written in terms of an existing
/// primitive.
/// - For MySQL, the value of `bytes` will depend on the return value of
/// `type_metadata` for the given SQL type. See [`MysqlType`] for details.
/// - For third party backends, consult that backend's documentation.
///
/// [`MysqlType`]: ../mysql/enum.MysqlType.html
///
/// ### Examples
///
/// Most implementations of this trait will be defined in terms of an existing
/// implementation.
///
/// ```rust
/// # use diesel::backend::{self, Backend};
/// # use diesel::sql_types::*;
/// # use diesel::deserialize::{self, FromSql, FromSqlRow};
/// #
/// #[repr(i32)]
/// #[derive(Debug, Clone, Copy, FromSqlRow)] // 加 FromSqlRow 后才能用在 Selectable struct model 中
/// pub enum MyEnum {
/// A = 1,
/// B = 2,
/// }
///
/// impl<DB> FromSql<Integer, DB> for MyEnum // 实现从 diesel sql type Integer 到自定义类型 MyEnum 转换
/// where
/// DB: Backend,
/// i32: FromSql<Integer, DB>,
/// {
/// fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
/// match i32::from_sql(bytes)? {
/// 1 => Ok(MyEnum::A),
/// 2 => Ok(MyEnum::B),
/// x => Err(format!("Unrecognized variant {}", x).into()),
/// }
/// }
/// }
/// ```
#[diagnostic::on_unimplemented(
message = "cannot deserialize a value of the database type `{A}` as `{Self}`",
note = "double check your type mappings via the documentation of `{A}`"
)]
pub trait FromSql<A, DB: Backend>: Sized {
/// See the trait documentation.
fn from_sql(bytes: DB::RawValue<'_>) -> Result<Self>;
/// A specialized variant of `from_sql` for handling null values.
///
/// The default implementation returns an `UnexpectedNullError` for
/// an encountered null value and calls `Self::from_sql` otherwise
///
/// If your custom type supports null values you need to provide a
/// custom implementation.
#[inline(always)]
fn from_nullable_sql(bytes: Option<DB::RawValue<'_>>) -> Result<Self> {
match bytes {
Some(bytes) => Self::from_sql(bytes),
None => Err(Box::new(crate::result::UnexpectedNullError)),
}
}
}
24.3 QueryBuilder:从 Diesel AST 构造 SQL 查询字符串 #
- push_sql: 直接插入传入的 SQL 字符串;
- push_identifier:插入 SQL 标识符, 如表名、列名、变量名,带空格的名称等:
- Pg:“identify”;
- Mysql: `identify`;
- push_bind_param:插入 bind param 占位符或绑定值(绑定值是实现 QueryBuilder 类型的内部记录,不体现在方法参数中):
- Pg:内部维护一个递增的 index 值:插入 $i;
- Mysql:直接插入一个 ? 字符串;
- finish: 返回构造的 SQL 查询字符串;
pub trait QueryBuilder<DB: Backend> {
// Required methods
fn push_sql(&mut self, sql: &str);
fn push_identifier(&mut self, identifier: &str) -> QueryResult<()>;
fn push_bind_param(&mut self);
fn finish(self) -> String;
// Provided method
fn push_bind_param_value_only(&mut self) { ... }
}
// 举例:
fn generate_query(builder: &mut impl QueryBuilder) -> QueryResult<()> {
builder.push_sql("SELECT ");
builder.push_identifier("select")?; // Pg 使用列名 'select'
builder.push_sql(" FROM ");
builder.push_identifier("user")?; // Pg 使用表名 'user'
Ok(())
}
// 对于 Pg 生成的是:SELECT "select" FROM "user"
// bind param 举例:
fn generate_insert_query(builder: &mut impl QueryBuilder) -> QueryResult<()> {
builder.push_sql("INSERT INTO users (name, age) VALUES (");
builder.push_bind_param()?; // mysql 用占位符 `?` 代替实际的 'name' 值
builder.push_sql(", ");
builder.push_bind_param()?; // mysql 用占位符 `?` 代替实际的 'age' 值
builder.push_sql(")");
Ok(())
}
// 对于 Mysql 生成的是:INSERT INTO users (name, age) VALUES (?, ?)
使用 bind param 的优点:
- SQL 安全性: 使用绑定参数可以有效防止 SQL 注入,因为参数是与查询逻辑分开的,SQL 引擎会将参数当作数据而不是查询的一部分。
- 性能优化: 通过预编译的 SQL 语句模板,执行时只需替换参数值,而无需重新解析整个 SQL 语句。
24.4 QueryFragment/AstPass/ToSql #
QueryBuidler 是数据库 Backend 实现的,用户一般不直接使用它,用户一般和 QueryFragment 的 walk_ast() 方法打交道,该方法的参数是 Struct diesel::query_builder::AstPass 类型,它封装了 QueryBuilder.
QueryFragment 使用 walk_ast() 方法在传入的 pass 参数(AstPass struct 类型)中添加新的 SQL 语句片段 (所以称为 Fragment, 但也可以是完整的 SQL 语句):
- AstPass 类型封装了 QueryBuilder 的 push_sql/push_identifier/push_bind_param() 等方法;
- push_bind_param() 方法压入实现 U: ToSql<T, DB> 的 U 类型绑定参数值;
在 T 实现 QueryFragment 的情况下, diesel 默认为 &T, 一系列数量的 tuple (T, xxx), Option<T>, Box<T> 实现了 QueryFragment.
LimitOffsetClause
BoxedLimitOffsetClause
TransactionBuilder
BoxedSelectStatement
NoFromClause
NoLimitClause
NoOffsetClause
SqlQuery<Inner>: Inner: QueryFragment<DB>,
BoxedSqlQuery<'_, DB, Query>: Inner: QueryFragment<DB>,
impl<DB, T> QueryFragment<DB> for CollectedQuery<T>
where
DB: Backend + DieselReserveSpecialization,
for<'a> <DB as Backend>::BindCollector<'a>: MoveableBindCollector<DB, BindData = T>,
now
today
LimitClause<Expr>: Expr: QueryFragment<DB>,
OffsetClause<Expr>
ReturningClause<Expr>
SelectStatement
LimitOffsetClause
impl<Query, Value, DB> QueryFragment<DB> for UncheckedBind<Query, Value>
where
DB: Backend,
Query: QueryFragment<DB>,
Value: QueryFragment<DB>,
impl<T, U, Op, Ret, DB> QueryFragment<DB> for InsertStatement<T, U, Op, Ret>
where
DB: Backend + DieselReserveSpecialization,
T: Table,
T::FromClause: QueryFragment<DB>,
U: QueryFragment<DB> + CanInsertInSingleQuery<DB>,
Op: QueryFragment<DB>,
Ret: QueryFragment<DB>,
impl<T, U, Ret, DB> QueryFragment<DB> for DeleteStatement<T, U, Ret>
where
DB: Backend + DieselReserveSpecialization,
T: Table,
FromClause<T>: QueryFragment<DB>,
U: QueryFragment<DB>,
Ret: QueryFragment<DB>,
impl<T, U, V, Ret, DB> QueryFragment<DB> for UpdateStatement<T, U, V, Ret>
where
DB: Backend + DieselReserveSpecialization,
T: Table,
T::FromClause: QueryFragment<DB>,
U: QueryFragment<DB>,
V: QueryFragment<DB>,
Ret: QueryFragment<DB>,
impl<T, U, DB> QueryFragment<DB> for Table<T, U>
where
DB: Backend,
T: Borrow<str>,
U: Borrow<str>,
impl<T, U, ST, DB> QueryFragment<DB> for Column<T, U, ST>
where
DB: Backend,
T: QueryFragment<DB>,
U: Borrow<str>,
impl<DB> QueryFragment<DB> for table
where
DB: Backend,
<table as StaticQueryFragment>::Component: QueryFragment<DB>,
QueryFragment 使用 collect_binds() 方法在传入的 out 参数(BindCollector 类型)中添加绑定变量值, 这些绑定参数值是调用 AstPass::push_bind_param() 方法时添加到实现 QueryFragment trait 的对象内部的:
- BindCollector 实现了 SQL 查询语句和绑定变量的分离, 前者可以被解析缓存, 重复使用.
- 执行 LoadConnection 的 Load() 方法内部会创建 BindCollector, 并调用 collect_binds() 方法来收集绑定参数信息, 后续再执行解析后的 statement.execute() 方法时传入 BindCollector;
pub trait QueryFragment<DB: Backend, SP = NotSpecialized> {
// 调用该方法时,向 pass 对象追加新的 SQL 语句片段.
fn walk_ast<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()>;
// Provided methods
fn to_sql(&self, out: &mut DB::QueryBuilder, backend: &DB, ) -> QueryResult<()>
// 将 bind params(一般来源于 walk_ast 的参数 AstPass 的 push_bind_param() 方法)更新到 out 参
// 数对应的 BindCollector 中
fn collect_binds<'b>(&'b self, out: &mut DB::BindCollector<'b>, metadata_lookup: &mut DB::MetadataLookup, backend: &'b DB,) -> QueryResult<()>
fn is_safe_to_cache_prepared(&self, backend: &DB) -> QueryResult<bool>
fn is_noop(&self, backend: &DB) -> QueryResult<bool>
}
示例:
// 示例: 调用 walk_ast(out) 方法后, out 中将保存老的 + 新追加的 SQL 语句.
impl<Left, Right, DB> QueryFragment<DB> for And<Left, Right>
where DB: Backend, Left: QueryFragment<DB>, Right: QueryFragment<DB>,
{
// 在 out 中保存根据 And 对象生成的 SQL 查询语句片段
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
// 将 left 语句追加到 out
self.left.walk_ast(out.reborrow())?;
// 加 sql 字符串语句
out.push_sql(" AND ");
// 将 right 语句追加到 out
self.right.walk_ast(out.reborrow())?;
Ok(())
}
}
#[test]
fn check_sql_query_increments_bind_count() {
use crate::query_builder::{AstPass, AstPassToSqlOptions, QueryFragment};
use crate::sql_types::*;
// 使用 raw SQL 构造一个 QueryFragment
let query = crate::sql_query("SELECT $1, $2, $3")
.bind::<Integer, _>(42)
.bind::<Integer, _>(3)
.bind::<Integer, _>(342);
let mut query_builder = PgQueryBuilder::default();
{
// 创建缺省 Option 和 QueryBuilder 创建一个 AstPass
let mut options = AstPassToSqlOptions::default();
// 使用 crate 内部方法 AstPass::to_sql() 来创建一个 AstPass
let ast_pass = AstPass::<crate::pg::Pg>::to_sql(&mut query_builder, &mut options, &Pg);
// 调用 QueryFragment 的 walk_ast() 方法
query.walk_ast(ast_pass).unwrap();
}
assert_eq!(query_builder.bind_idx, 3);
assert_eq!(query_builder.sql, "SELECT $1, $2, $3");
}
AstPass 的 push_bind_param() 方法是用来指定 SQL 查询的绑定参数, 它使用 ToSql trait 将传入的 Rust 类型值 U 转换为 diesel sql_type 类型 T:
pub struct AstPass<'a, 'b, DB> where DB: Backend, DB::QueryBuilder: 'a, DB::MetadataLookup: 'a, 'b: 'a
// AstPaas 提供了如下方法:
pub fn reborrow(&mut self) -> AstPass<'_, 'b, DB>
pub fn unsafe_to_cache_prepared(&mut self)
pub fn push_sql(&mut self, sql: &str)
pub fn push_identifier(&mut self, identifier: &str) -> QueryResult<()>
// 压入实现 ToSql<T, DB> 的 U 类型绑定参数值
pub fn push_bind_param<T, U>(&mut self, bind: &'b U) -> QueryResult<()>
where DB: HasSqlType<T>, U: ToSql<T, DB> + ?Sized
pub fn push_bind_param_value_only<T, U>( &mut self, bind: &'b U, ) -> QueryResult<()>
where DB: HasSqlType<T>, U: ToSql<T, DB> + ?Sized
pub fn push_bind_collector_data<MD>( &mut self, bind_collector_data: &MD,) -> QueryResult<()>
where DB: Backend, for<'bc> DB::BindCollector<'bc>: MoveableBindCollector<DB, BindData = MD>
pub fn backend(&self) -> &DB
pub fn should_skip_from(&self) -> bool
ToSql trait 将 Rust 类型值转换为 Diesel 值类型, 如 Interger, Int4.
- diesel 为常用 Rust 类型实现了 ToSql. 对于用户自定义类型, 可以通过这些通用来实现 ToSql;
- 为自定义类型实现 ToSql 时, 需要使用 derive macro 实现 AsSelect;
/// Serializes a single value to be sent to the database.
///
/// The output is sent as a bind parameter, and the data must be written in the
/// expected format for the given backend.
///
/// When possible, implementations of this trait should prefer using an existing
/// implementation, rather than writing to `out` directly. (For example, if you
/// are implementing this for an enum, which is represented as an integer in the
/// database, you should use `i32::to_sql(x, out)` instead of writing to `out`
/// yourself.)
///
/// Any types which implement this trait should also
/// [`#[derive(AsExpression)]`](derive@crate::expression::AsExpression).
///
/// ### Backend specific details
///
/// - For PostgreSQL, the bytes will be sent using the binary protocol, not text.
/// - For SQLite, all implementations should be written in terms of an existing
/// `ToSql` implementation.
/// - For MySQL, the expected bytes will depend on the return value of
/// `type_metadata` for the given SQL type. See [`MysqlType`] for details.
/// - For third party backends, consult that backend's documentation.
///
/// [`MysqlType`]: ../mysql/enum.MysqlType.html
///
/// ### Examples
///
/// Most implementations of this trait will be defined in terms of an existing
/// implementation.
///
/// ```rust
/// # use diesel::backend::Backend;
/// # use diesel::expression::AsExpression;
/// # use diesel::sql_types::*;
/// # use diesel::serialize::{self, ToSql, Output};
/// # use std::io::Write;
/// #
/// #[repr(i32)]
/// #[derive(Debug, Clone, Copy, AsExpression)]
/// #[diesel(sql_type = Integer)]
/// pub enum MyEnum {
/// A = 1,
/// B = 2,
/// }
///
/// impl<DB> ToSql<Integer, DB> for MyEnum
/// where
/// DB: Backend,
/// i32: ToSql<Integer, DB>,
/// {
/// fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> serialize::Result {
/// match self {
/// MyEnum::A => 1.to_sql(out),
/// MyEnum::B => 2.to_sql(out),
/// }
/// }
/// }
/// ```
///
/// Using temporary values as part of the `ToSql` implementation requires additional
/// work.
///
/// Backends using [`RawBytesBindCollector`] as [`BindCollector`] copy the serialized values as part
/// of `Write` implementation. This includes the `Mysql` and the `Pg` backend provided by diesel.
/// This means existing `ToSql` implementations can be used even with
/// temporary values. For these it is required to call
/// [`Output::reborrow`] to shorten the lifetime of the `Output` type correspondingly.
///
/// ```
/// # use diesel::backend::Backend;
/// # use diesel::expression::AsExpression;
/// # use diesel::sql_types::*;
/// # use diesel::serialize::{self, ToSql, Output};
/// # use std::io::Write;
/// #
/// #[repr(i32)]
/// #[derive(Debug, Clone, Copy, AsExpression)]
/// #[diesel(sql_type = Integer)]
/// pub enum MyEnum {
/// A = 1,
/// B = 2,
/// }
///
/// # #[cfg(feature = "postgres")]
/// impl ToSql<Integer, diesel::pg::Pg> for MyEnum
/// where
/// i32: ToSql<Integer, diesel::pg::Pg>,
/// {
/// fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, diesel::pg::Pg>) -> serialize::Result {
/// let v = *self as i32;
/// <i32 as ToSql<Integer, diesel::pg::Pg>>::to_sql(&v, &mut out.reborrow())
/// }
/// }
/// ````
///
/// For any other backend the [`Output::set_value`] method provides a way to
/// set the output value directly. Checkout the documentation of the corresponding
/// `BindCollector::Buffer` type for provided `From<T>` implementations for a list
/// of accepted types. For the `Sqlite` backend see `SqliteBindValue`.
///
/// ```
/// # use diesel::backend::Backend;
/// # use diesel::expression::AsExpression;
/// # use diesel::sql_types::*;
/// # use diesel::serialize::{self, ToSql, Output, IsNull};
/// # use std::io::Write;
/// #
/// #[repr(i32)]
/// #[derive(Debug, Clone, Copy, AsExpression)]
/// #[diesel(sql_type = Integer)]
/// pub enum MyEnum {
/// A = 1,
/// B = 2,
/// }
///
/// # #[cfg(feature = "sqlite")]
/// impl ToSql<Integer, diesel::sqlite::Sqlite> for MyEnum
/// where
/// i32: ToSql<Integer, diesel::sqlite::Sqlite>,
/// {
/// fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, diesel::sqlite::Sqlite>) -> serialize::Result {
/// out.set_value(*self as i32);
/// Ok(IsNull::No)
/// }
/// }
/// ````
pub trait ToSql<A, DB: Backend>: fmt::Debug {
/// See the trait documentation.
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> Result;
}
impl<'a, A, T, DB> ToSql<A, DB> for &'a T where DB: Backend, T: ToSql<A, DB> + ?Sized,
{
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> Result {
(*self).to_sql(out)
}
}
QueryFragment 在 Trait diesel::query_dsl::methods::ExecuteDsl 和 Trait diesel::query_dsl::methods::LoadQuery 中使用:
24.5 FromSql/ToSql 和自定义类型 #
24.6 ExecuteDsl: 执行 SQL 语句,返回受影响的行数 #
diesel 为 QueryFragment 实现了 ExecuteDsl.
execte() 方法调用 Connection 的 execute_returning_count() 来实现:
- RunQueryDsl 的 execute() 方法内部使用该 trait。
// ExecuteDsl 执行 SQL 语句,返回受影响的行数。
pub trait ExecuteDsl<Conn: Connection<Backend = DB>, DB: Backend = <Conn as Connection>::Backend>: Sized {
// Required method
fn execute(query: Self, conn: &mut Conn) -> QueryResult<usize>;
}
// Diesel 为 QueryFragment 实现了 ExecuteDsl trait,内部实现上调用的是 Connection 的
// execute_returning_count 方法。
impl<Conn, DB, T> ExecuteDsl<Conn, DB> for T
where
Conn: Connection<Backend = DB>,
DB: Backend,
T: QueryFragment<DB> + QueryId,
{
fn execute(query: T, conn: &mut Conn) -> Result<usize, Error> {
conn.execute_returning_count(&query) // 调用 Connection 方法
}
}
24.7 AsQuery/Query/CompatibleType #
Query 封装了 diesel sql_type 类型.
AsQuery 也封装了 diesel sql_type 类型, 并且它的 as_query() 方法返回 Query;
/// A complete SQL query with a return type.
///
/// This can be a select statement, or a command such as `update` or `insert`
/// with a `RETURNING` clause. Unlike [`Expression`], types implementing this
/// trait are guaranteed to be executable on their own.
///
/// A type which doesn't implement this trait may still represent a complete SQL
/// query. For example, an `INSERT` statement without a `RETURNING` clause will
/// not implement this trait, but can still be executed.
///
/// [`Expression`]: crate::expression::Expression
pub trait Query {
/// The SQL type that this query represents.
///
/// This is the SQL type of the `SELECT` clause for select statements, and
/// the SQL type of the `RETURNING` clause for insert, update, or delete
/// statements.
type SqlType; // Diesel sql_type 类型, 如 Interger/Int4/Text 等
}
/// Types that can be converted into a complete, typed SQL query.
///
/// This is used internally to automatically add the right select clause when
/// none is specified, or to automatically add `RETURNING *` in certain contexts.
///
/// A type which implements this trait is guaranteed to be valid for execution.
pub trait AsQuery {
/// The SQL type of `Self::Query`
type SqlType;
/// What kind of query does this type represent?
type Query: Query<SqlType = Self::SqlType>;
/// Converts a type which semantically represents a SQL query into the
/// actual query being executed. See the trait level docs for more.
// This method is part of our public API, so we won't change the name to just appease clippy
// (Also the trait is literally named `AsQuery` so naming the method similarity is fine)
#[allow(clippy::wrong_self_convention)]
fn as_query(self) -> Self::Query;
}
// 为 Query 实现 AsQuery
impl<T: Query> AsQuery for T {
type SqlType = <T as Query>::SqlType;
type Query = T;
fn as_query(self) -> <T as AsQuery>::Query {
self
}
}
table!() 宏为 table 自动实现 AsQuery trait, 对应的 Query 关联类型为 SelectStatement: 封装了 table
各 field 的 diesel sql_type 组成的 tuple 类型, 如: pub type SqlType = (Int4,Varchar,Text,Bool,);
pub type SqlType = (Int4,Varchar,Text,Bool,);
impl diesel::query_builder::AsQuery for table {
type SqlType = SqlType;
type Query = diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<Self>> ;
fn as_query(self) -> Self::Query {
diesel::internal::table_macro::SelectStatement::simple(self)
}
}
CompatibleType 是 SqlType 的封装, 实现将 diesetl SqlType ST 类型转换为 Rust U 类型:
pub trait CompatibleType<U, DB> {
type SqlType;
}
// 使用 FromSqlRow 将 ST 转换为 U 结果类型
impl<ST, U, DB> CompatibleType<U, DB> for ST
where
DB: Backend,
ST: SqlType + crate::sql_types::SingleValue,
U: FromSqlRow<ST, DB>, // 使用 FromSqlRow 将 ST 转换为 U 类型
{
type SqlType = ST;
}
// 将 ST 转换为 Selectable 类型 U
pub struct SelectBy<T: Selectable<DB>, DB: Backend> {
selection: T::SelectExpression,
p: std::marker::PhantomData<(T, DB)>,
}
impl<U, DB, E, ST> CompatibleType<U, DB> for SelectBy<U, DB>
where
DB: Backend,
ST: SqlType + TypedExpressionType,
U: Selectable<DB, SelectExpression = E>,
E: Expression<SqlType = ST>,
U: FromSqlRow<ST, DB>, // 从 Sql Row 类型 ST 转换为 U
{
type SqlType = ST;
}
24.8 LoadQuery/LoadConnection: 返回 Row 迭代器/AsQuery #
table!() 为 table 实现了 AsQuery.
diesel 为 QueryFragment 实现了 AsQuery.
diesel 为所有实现 AsQuery 的类型实现了 LoadQuery.
RunQueryDsl 的 loads()/get_result()/get_results()/first() 方法内部使用该 trait。
核心是调用 DB Backend 实现的 LoadConnection trait 的 load() 方法,返回一个可迭代的 Cursor 对象。
// LoadQuery 返回 Row 迭代器,可以返回单个或多个 Row 对象对应的 U 类型。
pub trait LoadQuery<'query, Conn, U, B = DefaultLoadingMode>: RunQueryDsl<Conn> {
type RowIter<'conn>: Iterator<Item = QueryResult<U>> where Conn: 'conn;
// Required method
fn internal_load(self, conn: &mut Conn) -> QueryResult<Self::RowIter<'_>>;
}
// 为 diesel sql_type 的 T 类型使用 FromSqlRow 转换为 U 类型的 RowIter 迭代器.
impl<'query, Conn, T, U, DB, B> LoadQuery<'query, Conn, U, B> for T
where
Conn: Connection<Backend = DB> + LoadConnection<B>,
T: AsQuery + RunQueryDsl<Conn>,
T::Query: QueryFragment<DB> + QueryId + 'query,
T::SqlType: CompatibleType<U, DB>,
DB: Backend + QueryMetadata<T::SqlType> + 'static,
U: FromSqlRow::SqlType, DB> + 'static,
<T::SqlType as CompatibleType<U, DB>>::SqlType: 'static,
{
type RowIter<'conn> = LoadIter<
U,
<Conn as LoadConnection<B>>::Cursor<'conn, 'query>,
<T::SqlType as CompatibleType<U, DB>>::SqlType, // 将 sql_type 的 T 类型转换为 U 类型
DB,> where Conn: 'conn;
fn internal_load(self, conn: &mut Conn) -> QueryResult<Self::RowIter<'_>> {
Ok(LoadIter {
cursor: conn.load(self.as_query())?, // 调用 Connection 实现的 LoadConnection 的 load() 方法
_marker: Default::default(),
})
}
}
传给 internal_load 的 conn 是实现了 LoadConnection trait 的 Connection。它将传入的 QueryFragment 类型 T 转换为可迭代的 Cursor, 迭代的元素类型是 Row 类型:
pub trait LoadConnection<B = DefaultLoadingMode>: Connection {
type Cursor<'conn, 'query>: Iterator<Item = QueryResult::Row<'conn, 'query>>> where Self: 'conn;
type Row<'conn, 'query>: Row<'conn, Self::Backend> where Self: 'conn;
// Required method
fn load<'conn, 'query, T>( &'conn mut self, source: T, ) -> QueryResult<Self::Cursor<'conn, 'query>>
where T: Query + QueryFragment<Self::Backend> + QueryId + 'query,
Self::Backend: QueryMetadata<T::SqlType>;
}
以 Pg 为例:https://docs.diesel.rs/2.2.x/src/diesel/pg/connection/mod.rs.html#244
- 根据实现 QueryFragment 的 diesel sql_type 类型 T 值 source 来查询 DB, 返回可迭代的 Row 记录.
- 内部创建一个 RawBytesBindCollector 类型对象, rranhou
impl<B> LoadConnection<B> for PgConnection where Self: self::private::PgLoadingMode<B>,
{
type Cursor<'conn, 'query> = <Self as self::private::PgLoadingMode<B>>::Cursor<'conn, 'query>;
type Row<'conn, 'query> = <Self as self::private::PgLoadingMode<B>>::Row<'conn, 'query>;
fn load<'conn, 'query, T>(&'conn mut self, source: T,) -> QueryResult<Self::Cursor<'conn, 'query>>
where
// T 是 实现 QueryFragment 的 diesel sql_type 类型
T: Query + QueryFragment<Self::Backend> + QueryId + 'query,
Self::Backend: QueryMetadata<T::SqlType>,
{
self.with_prepared_query(source, false, |stmt, params, conn, source| {
// 该 closure 函数执行实际的 SQL 查询并返回结果。stmt 是已解析的 prepared statement,
// params 是实际 binds 值。
use self::private::PgLoadingMode;
// 执行 stmt 的 execute 方法
let result = stmt.execute(&mut conn.raw_connection, ¶ms, Self::USE_ROW_BY_ROW_MODE);
let result = update_transaction_manager_status(
result,
conn,
&crate::debug_query(&source),
false,
)?;
// 返回结果对应的 Cursor,它实现了 Iterator,迭代时返回下一个查询结果。
Self::get_cursor(conn, result, source)
})
}
}
// with_prepared_query() 方法先准备 bind params,然后查找 source 对应的 prepared statement, 最后调
// 用 f closure 函数来返回可迭代的结果。
fn with_prepared_query<'conn, T, R, E>(
&'conn mut self,
source: T,
execute_returning_count: bool,
f: impl FnOnce(
MaybeCached<'_, Statement>, // statement
Vec<Option<Vec<u8>>>, // params 是传递给 statement 的绑定参数列表, 各参数值是 bytes 类型.
&'conn mut ConnectionAndTransactionManager, // conn
T, // 实现 QueryFragment 的 diesel sql_type 类型
) -> Result<R, E>,
) -> Result<R, E> where T: QueryFragment<Pg> + QueryId, E: From<crate::result::Error>
{
self.connection_and_transaction_manager
.instrumentation
.on_connection_event(InstrumentationEvent::StartQuery {
query: &crate::debug_query(&source),
});
// 将 source QueryFragment 中实际传入的 bind params 参数值更新到 bind_collector 中。
let mut bind_collector = RawBytesBindCollector::<Pg>::new();
// 调用 QueryFragment collect_binds() 方法, 将 source 中记录的绑定参数更新到 bind_collector 中.
source.collect_binds(&mut bind_collector, self, &Pg)?;
let binds = bind_collector.binds; // 绑定参数集合
let metadata = bind_collector.metadata; // 各占位符对应的 SQL Type metadata 信息
let cache_len = self.statement_cache.len();
let cache = &mut self.statement_cache;
let conn = &mut self.connection_and_transaction_manager.raw_connection;
// 返回 source 对应的缓存的 prepared statement
let query = cache.cached_statement(
&source,
&Pg,
&metadata,
|sql, _| {
let query_name = if source.is_safe_to_cache_prepared(&Pg)? {
Some(format!("__diesel_stmt_{cache_len}"))
} else {
None
};
// 生成 sql 对应的 prepared statement,并按需缓存。
Statement::prepare(conn, sql, query_name.as_deref(), &metadata)
},
&mut self.connection_and_transaction_manager.instrumentation,
);
if !execute_returning_count {
if let Err(ref e) = query {
self.connection_and_transaction_manager
.instrumentation
.on_connection_event(InstrumentationEvent::FinishQuery {
query: &crate::debug_query(&source),
error: Some(e),
});
}
}
// 根据返回的 prepared statement 和实际收集的 binds 值,执行 SQL 查询并返回结果。
f(
query?,
binds,
&mut self.connection_and_transaction_manager,
source,
)
}
impl PgLoadingMode<DefaultLoadingMode> for PgConnection {
const USE_ROW_BY_ROW_MODE: bool = false;
type Cursor<'conn, 'query> = Cursor;
type Row<'conn, 'query> = self::row::PgRow;
// get_cursor() 返回的 Cursor 提供返回下一个查询&执行结果记录的功能。
fn get_cursor<'conn, 'query>(
conn: &'conn mut ConnectionAndTransactionManager,
result: PgResult,
source: impl QueryFragment<Pg> + 'query,
) -> QueryResult<Self::Cursor<'conn, 'query>> {
update_transaction_manager_status(
Cursor::new(result, &mut conn.raw_connection),
conn,
&crate::debug_query(&source),
true,
)
}
}
// Cursor 提供返回下一个 result 结果的能力。
#[allow(missing_debug_implementations)]
pub struct Cursor {
current_row: usize,
db_result: Rc<PgResult>,
}
impl Cursor {
pub(super) fn new(result: PgResult, conn: &mut RawConnection) -> crate::QueryResult<Cursor> {
let next_res = conn.get_next_result()?;
debug_assert!(next_res.is_none());
Ok(Self {
current_row: 0,
db_result: Rc::new(result),
})
}
}
pub(crate) fn get_next_result(&self) -> Result<Option<PgResult>, Error> {
// PQgetResult() 是 PostgreSQL C 库提供的方法
let res = unsafe { PQgetResult(self.internal_connection.as_ptr()) };
if res.is_null() {
Ok(None)
} else {
let raw = RawResult::new(res, self)?;
Ok(Some(PgResult::new(raw, self)?))
}
}
pub type PGresult = pg_result; // PostgreSQL 的 C 库结果类型
pub(super) struct RawResult(NonNull<PGresult>);
impl RawResult {
#[allow(clippy::new_ret_no_self)]
fn new(ptr: *mut PGresult, conn: &RawConnection) -> QueryResult<Self> {
NonNull::new(ptr).map(RawResult).ok_or_else(|| {
Error::DatabaseError(
DatabaseErrorKind::UnableToSendCommand,
Box::new(conn.last_error_message()),
)
})
}
// ...
}
24.9 写入数据库 #
/// Construct a full SQL query using raw SQL.
///
/// This function exists for cases where a query needs to be written that is not
/// supported by the query builder. Unlike most queries in Diesel, `sql_query`
/// will deserialize its data by name, not by index. That means that you cannot
/// deserialize into a tuple, and structs which you deserialize from this
/// function will need to have `#[derive(QueryableByName)]`.
///
/// This function is intended for use when you want to write the entire query
/// using raw SQL. If you only need a small bit of raw SQL in your query, use
/// [`sql`](crate::dsl::sql()) instead.
///
/// Query parameters can be bound into the raw query using [`SqlQuery::bind()`].
///
/// # Safety
///
/// The implementation of `QueryableByName` will assume that columns with a
/// given name will have a certain type. The compiler will be unable to verify
/// that the given type is correct. If your query returns a column of an
/// unexpected type, the result may have the wrong value, or return an error.
///
/// # Examples
///
/// ```rust
/// # include!("../doctest_setup.rs");
/// #
/// # use schema::users;
/// #
/// # #[derive(QueryableByName, Debug, PartialEq)]
/// # struct User {
/// # id: i32,
/// # name: String,
/// # }
/// #
/// # fn main() {
/// # run_test_1().unwrap();
/// # run_test_2().unwrap();
/// # }
/// #
/// # fn run_test_1() -> QueryResult<()> {
/// # use diesel::sql_query;
/// # use diesel::sql_types::{Integer, Text};
/// #
/// # let connection = &mut establish_connection();
/// let users = sql_query("SELECT * FROM users ORDER BY id")
/// .load(connection);
/// let expected_users = vec![
/// User { id: 1, name: "Sean".into() },
/// User { id: 2, name: "Tess".into() },
/// ];
/// assert_eq!(Ok(expected_users), users);
/// # Ok(())
/// # }
///
/// # fn run_test_2() -> QueryResult<()> {
/// # use diesel::sql_query;
/// # use diesel::sql_types::{Integer, Text};
/// #
/// # let connection = &mut establish_connection();
/// # diesel::insert_into(users::table)
/// # .values(users::name.eq("Jim"))
/// # .execute(connection).unwrap();
/// # #[cfg(feature = "postgres")]
/// # let users = sql_query("SELECT * FROM users WHERE id > $1 AND name != $2");
/// # #[cfg(not(feature = "postgres"))]
/// // Checkout the documentation of your database for the correct
/// // bind placeholder
/// let users = sql_query("SELECT * FROM users WHERE id > ? AND name <> ?");
/// let users = users
/// .bind::<Integer, _>(1)
/// .bind::<Text, _>("Tess")
/// .get_results(connection);
/// let expected_users = vec![
/// User { id: 3, name: "Jim".into() },
/// ];
/// assert_eq!(Ok(expected_users), users);
/// # Ok(())
/// # }
/// ```
/// [`SqlQuery::bind()`]: crate::query_builder::SqlQuery::bind()
pub fn sql_query<T: Into<String>>(query: T) -> SqlQuery {
SqlQuery::from_sql(query.into())
}
#[derive(Debug, Clone)]
#[must_use = "Queries are only executed when calling `load`, `get_result` or similar."]
/// The return value of `sql_query`.
///
/// Unlike most queries in Diesel, `SqlQuery` loads its data by column name,
/// rather than by index. This means that you cannot deserialize this query into
/// a tuple, and any structs used must implement `QueryableByName`.
///
/// See [`sql_query`](crate::sql_query()) for examples.
pub struct SqlQuery<Inner = self::private::Empty> {
inner: Inner,
query: String,
}
impl<Inner> SqlQuery<Inner> {
pub(crate) fn new(inner: Inner, query: String) -> Self {
SqlQuery { inner, query }
}
/// Bind a value for use with this SQL query. The given query should have
/// placeholders that vary based on the database type,
/// like [SQLite Parameter](https://sqlite.org/lang_expr.html#varparam) syntax,
/// [PostgreSQL PREPARE syntax](https://www.postgresql.org/docs/current/sql-prepare.html),
/// or [MySQL bind syntax](https://dev.mysql.com/doc/refman/8.0/en/mysql-stmt-bind-param.html).
///
/// # Safety
///
/// This function should be used with care, as Diesel cannot validate that
/// the value is of the right type nor can it validate that you have passed
/// the correct number of parameters.
///
/// # Example
///
/// ```
/// # include!("../doctest_setup.rs");
/// #
/// # use schema::users;
/// #
/// # #[derive(QueryableByName, Debug, PartialEq)]
/// # struct User {
/// # id: i32,
/// # name: String,
/// # }
/// #
/// # fn main() {
/// # use diesel::sql_query;
/// # use diesel::sql_types::{Integer, Text};
/// #
/// # let connection = &mut establish_connection();
/// # diesel::insert_into(users::table)
/// # .values(users::name.eq("Jim"))
/// # .execute(connection).unwrap();
/// # #[cfg(feature = "postgres")]
/// # let users = sql_query("SELECT * FROM users WHERE id > $1 AND name != $2");
/// # #[cfg(not(feature = "postgres"))]
/// let users = sql_query("SELECT * FROM users WHERE id > ? AND name <> ?")
/// # ;
/// # let users = users
/// .bind::<Integer, _>(1)
/// .bind::<Text, _>("Tess")
/// .get_results(connection);
/// let expected_users = vec![
/// User { id: 3, name: "Jim".into() },
/// ];
/// assert_eq!(Ok(expected_users), users);
/// # }
/// ```
pub fn bind<ST, Value>(self, value: Value) -> UncheckedBind<Self, Value, ST> {
UncheckedBind::new(self, value)
}
/// Internally boxes future calls on `bind` and `sql` so that they don't
/// change the type.
///
/// This allows doing things you otherwise couldn't do, e.g. `bind`ing in a
/// loop.
pub fn into_boxed<'f, DB: Backend>(self) -> BoxedSqlQuery<'f, DB, Self> {
BoxedSqlQuery::new(self)
}
/// Appends a piece of SQL code at the end.
pub fn sql<T: AsRef<str>>(mut self, sql: T) -> Self {
self.query += sql.as_ref();
self
}
}
impl SqlQuery {
pub(crate) fn from_sql(query: String) -> SqlQuery {
Self {
inner: self::private::Empty,
query,
}
}
}
impl<DB, Inner> QueryFragment<DB> for SqlQuery<Inner>
where
DB: Backend + DieselReserveSpecialization,
Inner: QueryFragment<DB>,
{
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
out.unsafe_to_cache_prepared();
self.inner.walk_ast(out.reborrow())?;
out.push_sql(&self.query);
Ok(())
}
}
impl<Inner> QueryId for SqlQuery<Inner> {
type QueryId = ();
const HAS_STATIC_QUERY_ID: bool = false;
}
impl<Inner> Query for SqlQuery<Inner> {
type SqlType = Untyped;
}
impl<Inner, Conn> RunQueryDsl<Conn> for SqlQuery<Inner> {}
#[derive(Debug, Clone, Copy)]
#[must_use = "Queries are only executed when calling `load`, `get_result` or similar."]
/// Returned by the [`SqlQuery::bind()`] method when binding a value to a fragment of SQL.
///
pub struct UncheckedBind<Query, Value, ST> {
query: Query,
value: Value,
_marker: PhantomData<ST>,
}
impl<Query, Value, ST> UncheckedBind<Query, Value, ST> {
pub fn new(query: Query, value: Value) -> Self {
UncheckedBind {
query,
value,
_marker: PhantomData,
}
}
pub fn bind<ST2, Value2>(self, value: Value2) -> UncheckedBind<Self, Value2, ST2> {
UncheckedBind::new(self, value)
}
pub fn into_boxed<'f, DB: Backend>(self) -> BoxedSqlQuery<'f, DB, Self> {
BoxedSqlQuery::new(self)
}
/// Construct a full SQL query using raw SQL.
///
/// This function exists for cases where a query needs to be written that is not
/// supported by the query builder. Unlike most queries in Diesel, `sql_query`
/// will deserialize its data by name, not by index. That means that you cannot
/// deserialize into a tuple, and structs which you deserialize from this
/// function will need to have `#[derive(QueryableByName)]`.
///
/// This function is intended for use when you want to write the entire query
/// using raw SQL. If you only need a small bit of raw SQL in your query, use
/// [`sql`](dsl::sql()) instead.
///
/// Query parameters can be bound into the raw query using [`SqlQuery::bind()`].
///
/// # Safety
///
/// The implementation of `QueryableByName` will assume that columns with a
/// given name will have a certain type. The compiler will be unable to verify
/// that the given type is correct. If your query returns a column of an
/// unexpected type, the result may have the wrong value, or return an error.
///
/// # Examples
///
/// ```rust
/// # include!("../doctest_setup.rs");
/// #
/// # use schema::users;
/// #
/// # #[derive(QueryableByName, Debug, PartialEq)]
/// # struct User {
/// # id: i32,
/// # name: String,
/// # }
/// #
/// # fn main() {
/// # use diesel::sql_query;
/// # use diesel::sql_types::{Integer, Text};
/// #
/// # let connection = &mut establish_connection();
/// # diesel::insert_into(users::table)
/// # .values(users::name.eq("Jim"))
/// # .execute(connection).unwrap();
/// # #[cfg(feature = "postgres")]
/// # let users = sql_query("SELECT * FROM users WHERE id > $1 AND name != $2");
/// # #[cfg(not(feature = "postgres"))]
/// let users = sql_query("SELECT * FROM users WHERE id > ? AND name <> ?")
/// # ;
/// # let users = users
/// .bind::<Integer, _>(1)
/// .bind::<Text, _>("Tess")
/// .get_results(connection);
/// let expected_users = vec![
/// User { id: 3, name: "Jim".into() },
/// ];
/// assert_eq!(Ok(expected_users), users);
/// # }
/// ```
/// [`SqlQuery::bind()`]: query_builder::SqlQuery::bind()
pub fn sql<T: Into<String>>(self, sql: T) -> SqlQuery<Self> {
SqlQuery::new(self, sql.into())
}
}
// ST 是 Diesel 的 sql_type 类型, 如 Int4, Integer 等
// Value 是绑定值的类型, 通过 ToSql<ST, DB> 转换为 ST 类型.
impl<Query, Value, ST, DB> QueryFragment<DB> for UncheckedBind<Query, Value, ST>
where
DB: Backend + HasSqlType<ST> + DieselReserveSpecialization,
Query: QueryFragment<DB>,
Value: ToSql<ST, DB>, // 绑定参数值 self.value 的类型 Value 可以通过 ToSql 转换为数据库 ST 类型.
{
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
self.query.walk_ast(out.reborrow())?;
// 将绑定参数值 self.value push 到 AstPass 中.
out.push_bind_param_value_only(&self.value)?;
Ok(())
}
}
impl<'a, 'b, DB> AstPass<'a, 'b, DB> where DB: Backend, 'b: 'a,
{
/// Push a value onto the given query to be sent separate from the SQL
///
/// This method affects multiple AST passes. It should be called at the
/// point in the raw SQL is inserted. This assumes the parameter placeholder
/// (`$1` on PG, `?` on other backends) is already inserted.
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
pub(crate) fn push_bind_param_value_only<T, U>(&mut self, bind: &'b U) -> QueryResult<()>
where DB: HasSqlType<T>, U: ToSql<T, DB> + ?Sized,
{
match self.internals {
AstPassInternals::CollectBinds { .. } | AstPassInternals::DebugBinds(..) => {
self.push_bind_param(bind)? // 调用 push_bind_param
}
AstPassInternals::ToSql(ref mut out, _) => {
out.push_bind_param_value_only();
}
_ => {}
}
Ok(())
}
/// Push a value onto the given query to be sent separate from the SQL
///
/// This method affects multiple AST passes. It should be called at the
/// point in the query where you'd want the parameter placeholder (`$1` on
/// PG, `?` on other backends) to be inserted.
pub fn push_bind_param<T, U>(&mut self, bind: &'b U) -> QueryResult<()>
where
DB: HasSqlType<T>,
U: ToSql<T, DB> + ?Sized,
{
match self.internals {
AstPassInternals::ToSql(ref mut out, _) => out.push_bind_param(),
AstPassInternals::CollectBinds {
ref mut collector,
ref mut metadata_lookup,
} => collector.push_bound_value(bind, metadata_lookup)?, // 调用 collector.push_bound_value
AstPassInternals::DebugBinds(ref mut f) => {
f.push(Box::new(bind));
}
AstPassInternals::IsNoop(ref mut result) => **result = false,
_ => {}
}
Ok(())
}
}
// 默认的 BindCollector 实现 RawBytesBindCollector, 它的 push_bound_value() 方法将 Rust 值类型转换
// 为 SQL bytes.
impl<'a, DB> BindCollector<'a, DB> for RawBytesBindCollector<DB>
where for<'b> DB: Backend<BindCollector<'b> = Self> + TypeMetadata,
{
type Buffer = ByteWrapper<'a>;
fn push_bound_value<T, U>( &mut self, bind: &U, metadata_lookup: &mut DB::MetadataLookup, ) -> QueryResult<()>
where
DB: HasSqlType<T>,
U: ToSql<T, DB> + ?Sized,
{
let mut bytes = Vec::new();
let is_null = {
let mut to_sql_output = Output::new(ByteWrapper(&mut bytes), metadata_lookup);
// 调用 bind.to_sql() 将 U 类型值序列化为 SQL bytes.
// bind 实现了 ToSql trait
bind.to_sql(&mut to_sql_output)
.map_err(SerializationError)?
};
let metadata = <DB as HasSqlType<T>>::metadata(metadata_lookup);
match is_null {
IsNull::No => self.binds.push(Some(bytes)),
IsNull::Yes => self.binds.push(None),
}
self.metadata.push(metadata);
Ok(())
}
}