Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds. -- wikipedia

dieselでbatch upsertをするには

Rustのdieselでbatch upsertをやる方法について、検索してもなかなか苦労したのですごく簡単にメモしておきます。バックエンドはPostgresを想定しています。MySQLでもこれができるかはわかりません。

結論

diesel::insert_into(...).values(...).on_conflict(...).do_update().set(...)でいける。

excluded句を入れたい場合

たとえばcolumn_aというカラムをexcludedしたいとします。最後のsetの呼び出しのところで、.set(column_a.eq(excluded(column_a)))とすれば実装できます。複数カラムの場合、setにタプルを投げ込むと指定できます。たとえばcolumn_acolumn_bcolumn_cに対してexcludedしたい場合、

diesel::insert_into(...)
                .values(...)
                .on_conflict(...)
                .do_update()
                .set((column_a.eq(excluded(column_a)), column_b.eq(excluded(column_b), column_c.eq(excluded(column_c))))

のようにです。

複数カラム指定を楽にする

ただカラムの数が増えてくるとだんだん間違えます。そこで、このタプルの生成はマクロに任せてしまうといいと思います。次のようなマクロを書きます。

macro_rules! excluded {
    ( $( $column:expr ),* ) => {{
        use diesel::{upsert::excluded, ExpressionMethods};
        ($( ($column.eq(excluded($column))) ),*)
    }}
}

これを先ほどのsetに投げ込みます。

diesel::insert_into(...)
               .values(...)
                 .on_conflict(...)
                .do_update()
                .set(excluded! {
                    column_a,
                    column_b,
                    column_c
                })

これで、あとはコードが展開されて勝手にexcludedを含むタプルが生成されます。結構便利だと思うので、困ったなと思ったらぜひ使ってみてください。