有没有一种更有效的方法来使用Rust和www.example.com将upsert的代码结构化到PostgreSQL中diesel.rs?

bfnvny8b  于 2023-04-05  发布在  PostgreSQL
关注(0)|答案(1)|浏览(94)

我是Rust的新手,但我有一个任务是使用diesel.rs将一些现有的数据工作流移植到Rust中。它们涉及对具有数千行的表进行UPSERT操作,并且通常〉100列,进入PostgreSQL数据库。
Diesel.rs文档包含几个UPSERT功能的示例。我基于Diesel.rs Getting Started指南中的posts示例创建了一个minimal example
考虑下面这个example的代码片段,我们定义了两个post并将它们插入到数据库中。然而,如果我们在数据模型中有很多列,那么这段代码的set(...)部分就会变得很长:

let post_set = vec![ Post {
    id: 1,
    title: "Hello World".to_string(),
    body: "This is my first post, with some edits".to_string(),
    published: true,
},
Post {
    id: 2,
    title: "Hello World 2".to_string(),
    body: "This is my second post".to_string(),
    published: false,
}];

// Run update/insert
// this works, can it be more DRY:
let rows_affected = diesel::insert_into(posts)
    .values(&post_set)
    .on_conflict(id)
    .do_update()
    // ==== CAN THIS SECTION BE MORE DRY? ====
    .set((
        id.eq(excluded(id)),
        title.eq(excluded(title)),
        body.eq(excluded(body)),
        published.eq(excluded(published)),
        // ** MANY MORE LINES HERE FOR TABLES WITH MANY COLUMNS ** 
    ))
    // ======================================
    .execute(connection).unwrap();

最终,这些代码可以工作,但是如果我们有很多列,感觉就像是在重复模型/模式中其他地方已经定义的内容,当然,如果模式在未来发生变化,这意味着需要更新更多的代码。
有没有一种DRY(不要重复自己)的方法来编写set(...)组件,以实现相同的结果?

编辑:AsChangeset

正如@kmdreko所提到的,我尝试过这样做:

#[derive(Queryable,Insertable,AsChangeset,Debug)]
#[diesel(table_name=schema::posts)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}

然后这个:

let rows_affected = diesel::insert_into(posts)
        .values(&post_set)
        .on_conflict(id)
        .do_update()
        // ==== CAN THIS SECTION BE MORE DRY? ====
        .set(&post_set)
        // ======================================
        .execute(connection)
        .unwrap();

所以这肯定会更DRY,这正是我所希望的,但我得到了一个编译器错误:

error[E0277]: the trait bound `&std::vec::Vec<Post>: diesel::AsChangeset` is not satisfied
   --> src/main.rs:76:14
    |
76  |         .set(&post_set)
    |          --- ^^^^^^^^^ the trait `diesel::AsChangeset` is not implemented for `&std::vec::Vec<Post>`
    |          |
    |          required by a bound introduced by this call
    |
    = help: the following other types implement trait `diesel::AsChangeset`:
              &'update Post
              (T0, T1)
              (T0, T1, T2)
              (T0, T1, T2, T3)
              (T0, T1, T2, T3, T4)
              (T0, T1, T2, T3, T4, T5)
              (T0, T1, T2, T3, T4, T5, T6)
              (T0, T1, T2, T3, T4, T5, T6, T7)
            and 13 others
note: required by a bound in `diesel::upsert::IncompleteDoUpdate::<diesel::query_builder::InsertStatement<T, U, Op, Ret>, Target>::set`

感觉很近,但我显然做错了什么。

p4tfgftt

p4tfgftt1#

严格地说,所有这些都不是重复,而是必要的配置。在某些情况下,你想对不同的列执行不同的操作,所以你需要在列级别控制它。这也是普通SQL的做法。这也是为什么你不能在那里传递值的向量的原因,底层查询只需要一个值。
现在我可以理解,这感觉像是不必要的重复,您可能希望有一个更短的解决方案。Diesel本身并不提供这一点,尽管如果有人为具体的API编写提案,他们可能会开放添加这样的东西。
独立于此,您可以编写自己的自定义函数,该函数接受一个列元组并将其转换为excluded构造的元组。这可能需要为各种大小的元组实现一个trait。之后,您可以将posts::all_columns作为参数传递给该函数,并让它生成正确的返回类型。

相关问题