更新

大多数应用程序属于称为“CRUD”应用程序的类别。 CRUD代表“创建,读取,更新,删除”。 Diesel为所有四个部分提供支持,但在本指南中,我们将介绍更新记录的所有不同方法。

通过调用diesel :: update(target).set(changes)来构造更新语句。 然后通过调用execute,get_resultget_results来运行生成的语句。

如果查看update文档,您会注意到参数的类型是实现IntoUpdateTarget的任何类型T. 您无需担心此特性的作用,但了解哪些类型可以实现它非常重要。 实现这种特性有三种。 第一个是表格。

如果我们有一个如下所示的表:

table! {
    posts {
        id -> BigInt,
        title -> Text,
        body -> Text,
        draft -> Bool,
        publish_at -> Timestamp,
        visit_count -> Integer,
    }
}

我们可以编写一个查询来发布所有帖子

use posts::dsl::*;

diesel::update(posts).set(draft.eq(false))

我们可以使用debug_query函数来检查生成的SQL。 您看到的输出可能与本指南略有不同,具体取决于您使用的后端。 如果我们运行println!(“{}”,debug_query :: <Pg,_>(&our_query));,我们将看到以下内容:

UPDATE "posts" SET "draft" = $1 -- binds: [false]

这与Rust代码几乎是一对一的(?表示SQL中的绑定参数,这里将用false替换)。 但是,想要更新整个表格的情况非常少见。 那么让我们来看看我们如何将其缩小范围。 您可以传递给更新的第二种是任何只有.filter调用的查询。 我们可以将更新范围仅限于触及publish_at过去的帖子,如下所示:

use posts::dsl::*;
use diesel::expression::dsl::now;

let target = posts.filter(publish_at.lt(now));
diesel::update(target).set(draft.eq(false))

这将生成以下SQL

UPDATE `posts` SET `draft` = ?
WHERE `posts`.`publish_at` < CURRENT_TIMESTAMP

最常见的更新查询仅限于单个记录。 因此,您可以传递给update的最后一种是实现可Identifiable trait的任何东西。 通过在结构上放置#[derive(Identifiable)]来实现Identifiable。 它表示与数据库表上的行一对一的任何结构。

如果我们想要一个映射到我们的posts表的结构,它看起来像这样:

#[derive(Identifiable)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub draft: bool,
    pub publish_at: SystemTime,
}

在此struct,每个数据库列有一个字段,但对于Identifiable来说重要的是它有id字段,它是我们表的主键。 由于我们的结构名称只是没有s的表名,因此我们不必显式提供表名。 如果我们的结构被命名为不同的东西,或者如果将其复数化比将s放在最后更复杂,我们必须通过添加#[table_name =“posts”]来指定表名。 我们在这里使用SystemTime,因为它在标准库中,但在实际应用中,我们可能想要使用更全功能的类型,例如来自chrono的类型,您可以通过启用Diesel上的chrono功能来实现。

如果我们想发布这篇文章,我们可以这样做:

diesel::update(&post).set(posts::draft.eq(false))

重要的是要注意我们总是传递对帖子的引用,而不是帖子本身。 当我们写 update(post)时,这相当于写update(posts.find(post.id))update(posts.filter(id.eq(post.id)))。 我们可以在生成的SQL中看到这个:

UPDATE `posts` SET `draft` = ? WHERE `posts`.`id` = ?

现在我们已经看到了指定我们想要更新的所有方法,让我们看一下提供数据来更新它的不同方法。 我们已经看到了第一种方法,即直接传递column.eq(value)。 到目前为止,我们刚刚在这里传递了Rust值,但我们实际上可以使用任何Diesel表达式。 例如,我们可以增加一列:

use posts::dsl::*;

diesel::update(posts).set(visit_count.eq(visit_count + 1))

生成的SQL

UPDATE `posts`
SET `visit_count` = `posts`.`visit_count` + 1

直接分配值对于小而简单的更改非常有用。 如果我们想以这种方式更新多个列,我们可以传递一个元组。

use posts::dsl::*;

diesel::update(posts)
  .set((
      title.eq("[REDACTED]"),
      body.eq("This post has been classified"),
  ))

这将生成您期望的SQL

UPDATE `posts` SET `title` = ?, `body` = ?

AsChangeset

虽然能够像这样直接更新列是很好的,但在处理具有多个字段的表单时,它很快就会变得很麻烦。 如果我们查看.set的签名,您会注意到约束是针对称为AsChangeset的特征。 这是diesel可以为我们提供的另一个特性。 我们可以将#[derive(AsChangeset)]添加到我们的Post结构中,这将让我们传递一个&Post来设set

diesel::update(posts::table).set(&post)

SQL将设置Post结构中除主键之外的每个字段。

UPDATE `posts` SET
    `title` = ?,
    `body` = ?,
    `draft` = ?,
    `publish_at` = ?,
    `visit_count` = ?

更改现有行的主键几乎不是您想要执行的操作,因此 #[derive(AsChangeset)]假定您要忽略它。 更改主键的唯一方法是使用.set(id.eq(new_id))显式地执行此操作。 但请注意,#[derive(AsChangeset)]没有表定义中的信息。 如果主键不是id,则还需要在结构上放置#[primary_key(your_primary_key)]

如果结构上有任何可选字段,则这些字段也会有特殊行为。 默认情况下,#[derive(AsChangeset)]将假定None表示您不希望分配该字段。 例如,如果我们有以下代码:

#[derive(AsChangeset)]
#[table_name="posts"]
struct PostForm<'a> {
    title: Option<&'a str>,
    body: Option<&'a str>,
}

diesel::update(posts::table)
    .set(&PostForm {
        title: None,
        body: Some("My new post"),
    })

这将生成以下SQL:

UPDATE `posts` SET `body` = ?

如果要分配NULL,可以在结构上指定#[changeset_options(treat_none_as_null =“true”)],或者可以使字段的类型为Option <Option <T >>。 Diesel目前没有提供一种方法来明确地将字段分配给其默认值,尽管可能在将来提供。

如果您正在使用PostgreSQL,所有这些选项也适用于INSERT ON CONFLICT DO UPDATE。 有关更多详细信息,请参阅upsert文档

执行您的查询

构建查询后,我们需要实际执行它。有几种不同的方法可以做到这一点,具体取决于你想要的类型。

运行查询的最简单方法是execute。此方法将运行您的查询,并返回受影响的行数。如果您只是想确保查询成功执行,并且不关心从数据库中获取任何内容,则应使用此方法。

对于您希望从数据库中获取数据的查询,我们需要使用get_resultget_results。如果您没有显式调用returning,这些方法将返回表中的所有列。与select语句上的load类似,您需要指定要反序列化的类型(使用#[derive(Queryable)]的元组或结构)。当您期望返回多个记录时,应该使用get_results。如果您只想要一条记录,则可以调用get_result

应该注意,默认情况下,从get_result接收0行被视为错误条件。如果要返回0或1行(例如,返回类型为QueryResult <Option <T >>),则需要调用.get_result(...).optional()

最后,如果你的结构同时包含#[derive(AsChangeset)]#[derive(Identifiable)],你将能够使用save_changes方法。与本指南中提到的其他方法不同,使用save_changes时不会显式构建查询。执行foo.save_changes(&conn)相当于执行diesel :: update(&foo).set(&foo).get_result(&conn)。与get_resultget_results一样,您需要指定您想要返回的类型。

本指南的所有代码都可以在此Diesel示例中以可执行的形式找到。