在本教程中,我们将详细了解 diesel print-schema 命令和 table! 宏的作用。对于 table!,我们将展示实际生成代码的简化版本,并解释每一部分对你的重要性。如果您曾经对生成的代码感到困惑,或者不明白 use schema::posts::dsl::* 代表什么,那么您现在处于正确的位置。要了解哪些类型在何处可用,另一种方法是打开您当前 crate 的 API 文档,通过运行 cargo docs --open 并导航到相关的模块。

diesel print-schema 是 Diesel CLI 提供的一个命令。 这个命令会建立数据库连接,查询所有表及其列的列表,并为每个表生成 table! 宏调用。 diesel print-schema 命令会跳过任何以 __(双下划线)开头的表名。 您可以通过配置让 Diesel 在运行迁移时自动重新执行 diesel print-schema 命令。有关详细信息,请参阅 Diesel CLI 配置文档

table! 是代码生成的主要部分。如果您想查看实际生成的确切代码,可以运行 cargo +nightly rustc -- -Z unstable-options --pretty=expanded。 但是,输出可能会非常混乱,其中包含很多对您来说并不直接相关的代码。 相反,我们将逐步展示这个输出的一个简化版本,这个版本只包含您会直接使用的代码部分。

在这个示例中,我们将查看由 table! 宏调用生成的代码:

table! {
    users {
        id -> Integer,
        name -> Text,
        hair_color -> Nullable<Text>,
    }
}

如果您只想查看完整的简化代码并自行浏览,您可以在本指南的末尾找到它。 table! 宏的输出总是一个与表名相同的 Rust 模块。 这个模块中最重要的部分是表本身的定义:

pub struct table;

这个结构体代表用户表,用于构造 SQL 查询语句。 在代码中通常以 users::table(有时简化为 users,稍后会详细介绍)的形式引用。 接下来,您会看到一个名为 columns 的模块,其中包含一个对应于表中每一列的结构体。

pub struct id;
pub struct name;
pub struct hair_color;

每个这些结构体都唯一地代表数据库表中的每一列,用于构造 SQL 查询。 每个这些结构体都将实现一个名为 Expression 的 trait, 该 trait 表示该列的 SQL 数据类型。

impl Expression for id {
    type SqlType = Integer;
}

impl Expression for name {
    type SqlType = Text;
}

impl Expression for hair_color {
    type SqlType = Nullable<Text>;
}

SqlType 类型是 Diesel 确保查询正确的关键。 这个类型将被 ExpressionMethods 用来决定哪些内容可以传递给像 eq 这样的方法,以及哪些不能。 它还会被 Queryable 用来确定当列出现在 select 子句中时,哪些类型可以被反序列化。

在 columns 模块中,您还会注意到一个特殊的列叫做 star。 它的定义类似如下所示:

pub struct star;

impl Expression for star {
    type SqlType = NotSelectable;
}

star 结构体在查询构建中表示 users.*。 这个结构体仅用于生成计数查询,不应在其他情况下使用。Diesel 通过索引而不是名称从查询中加载数据。为了确保我们实际上获取的是我们认为是的列的数据,Diesel 在实际需要获取数据时从不使用 *。相反,我们会生成一个明确的 select 子句,比如 SELECT users.id, users.name, users.hair_color

columns 模块中的所有内容都将被父模块重新导出。 这就是为什么我们可以直接引用列 users::id,而不需要使用 users::columns::id

pub use self::columns::*;

pub struct table;

pub mod columns {
    /* ... */
}

当每个查询都需要以 users:: 作为前缀时,查询语句往往会变得非常冗长。 为了避免这种情况,Diesel 还提供了一个方便的模块,名为 dsl

pub mod dsl {
    pub use super::columns::{id, name, hair_color};
    pub use super::table as users;
}

dsl 模块重新导出了 columns 模块中的所有内容(除了 star), 并且重新导出了表,但将其重命名为实际的表名。

这意味着,我们不需要写:

users::table
    .filter(users::name.eq("Sean"))
    .filter(users::hair_color.eq("black"))

而是可以写成:

users.filter(name.eq("Sean")).filter(hair_color.eq("black"))

dsl 模块只应该用于导入单个函数。 您不应该在模块顶部使用 use schema::users::dsl::*;。 像 #[derive(Insertable)] 这样的代码会假设 users 指向模块,而不是表结构。

由于在导入 use schema::users::dsl::*;star 无法访问, 它将被作为 table 的一个实例方法暴露出来。

impl table {
    pub fn star(&self) -> star {
        star
    }
}

接下来,有几个 trait 被应用于 table。 您通常不会直接与这些 trait 交互, 但它们是使用 query_dsl 模块 中 发现的许多查询构建函数的关键, 同时也支持与 insertupdatedelete 一起使用。

impl AsQuery for table {
    /* 省略 */
}

impl Table for table {
    /* 省略 */
}

impl IntoUpdateTarget for table {
    /* 省略 */
}

最后,有一些小的类型定义和常量被提出来, 目的是让您的开发工作更加便捷。

pub const all_columns: (id, name, hair_color) = (id, name, hair_color);

pub type SqlType = (Integer, Text, Nullable<Text>);

pub type BoxedQuery<'a, DB, ST = SqlType> = BoxedSelectStatement<'a, ST, table, DB>;

all_columns 只是一个包含表上所有列的元组。 当您没有明确指定查询的 select 语句时,它会被用来生成这个表的查询语句。 如果您想要引用 users::star 用于非计数查询以外的场景, 您可能需要使用 users::all_columns 而不是 users::star

SqlTypeall_columns 的 SQL 类型。 直接引用它的情况很少见, 但在实际需要时,它比 <<users::table as Table>::AllColumns as Expression>::SqlType 更加简洁。

最后,有一个用于引用从这个表构建的装箱查询的辅助类型。 这意味着,您不需要写 diesel::dsl::IntoBoxed<'static, users::table, Pg>, 而是可以写 users::BoxedQuery<'static, Pg>。 如果查询包含自定义的 select 子句,您还可以选择指定 SQL 类型。

这就是全部内容!以下是为此表生成的完整代码:

pub mod users {
    pub use self::columns::*;

    pub mod dsl {
        pub use super::columns::{id, name, hair_color};
        pub use super::table as users;
    }

    pub const all_columns: (id, name, hair_color) = (id, name, hair_color);

    pub struct table;

    impl table {
        pub fn star(&self) -> star {
            star
        }
    }

    pub type SqlType = (Integer, Text, Nullable<Text>);

    pub type BoxedQuery<'a, DB, ST = SqlType> = BoxedSelectStatement<'a, ST, FromClause<table>, DB>;

    impl AsQuery for table {
        /* body omitted */
    }

    impl Table for table {
        /* body omitted */
    }

    impl IntoUpdateTarget for table {
        /* body omitted */
    }

    pub mod columns {
        pub struct star;

        impl Expression for star {
            type SqlType = ();
        }

        pub struct id;

        impl Expression for id {
            type SqlType = Integer;
        }

        pub struct name;

        impl Expression for name {
            type SqlType = Text;
        }

        pub struct hair_color;

        impl Expression for hair_color {
            type SqlType = Nullable<Text>;
        }
    }
}