使用Rust和diesel.rs是否有更有效的方法来构建PostgreSQL的upsert代码? &g



我对Rust比较陌生,但我有一个任务,使用diesel.rs将一些现有的数据工作流移植到Rust。它们涉及对数千行表的UPSERT操作,并且经常>100列,进入PostgreSQL数据库。

柴油。rs文档包含了几个覆盖UPSERT功能的示例。我已经创建了一个最小的例子,基于posts的例子从柴油。rs入门指南。

考虑本示例中的以下代码片段,其中我们定义了两个帖子并将它们插入数据库。注意,对于这个有四列的示例,代码是很容易管理的;但是,如果数据模型中有很多列,那么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`

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

严格来说,这些都不是重复,而是必要的配置。在某些情况下,您希望对不同的列执行不同的操作,因此您需要在列级别对此进行控制。这也与普通SQL的功能非常相似。这也是为什么你不能在那里传递一个值向量的原因,底层查询只需要一个值。

现在我可以理解这感觉像是不必要的重复,并且您可能希望对此有一个更短的解决方案。Diesel本身并没有提供这样的功能,不过如果有人写了一个具体API的提案,他们可能会开放添加这样的功能。

独立地,您可以编写自己的自定义函数,该函数接受列元组并将其转换为excluded结构的元组。这可能需要为不同大小的元组实现一个trait。之后,您可以将posts::all_columns作为参数传递给该函数,并让它生成正确的返回类型。