5
4

More than 1 year has passed since last update.

entでUpsertを実装する(for PostgreSQL, SQLite3)

Last updated at Posted at 2022-01-24

はじめに

  • 仕事でORMにentを使いAPIを作成しているんですが、Upsertを実装する際にPostgresだとつまずいた部分があったため、Tipsになればと良いなと思いやったことを書き残していきます

発生した問題

  • Upsertを実装したものの
    • SQLiteでは正常に動作する
    • PostgreSQLでは下の様なエラーが発生する
  • CreatedAtは更新したくないのに更新されてしまう
ERROR: ON CONFLICT DO UPDATE requires inference specification or constraint name (SQLSTATE 42601)

今、こんな問題が起きてる方は 5. から見ると良いと思います:thumbsup:
5. Upsertを実装する場合

目次

  1. ent v0.9.0からUpsertが利用できる様になりました
  2. generate.goにsql/upsert機能フラグを追加します
  3. Create(データをinsert)する場合
  4. 特定のやつをUpdateする場合
  5. Upsertを実装する場合
  6. まとめ
  7. 参考

ent v0.9.0からUpsertが利用できる様になりました

↑ この記事を参考に書いていけばSQLite3の場合は期待通りUpsertの実装を行うことが可能です

下記にUpsertを実装する手順を書いていきます

generate.goに sql/upsert 機能フラグを追加します

/ent/generate.go
package ent

- //go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema
+ //go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/upsert ./schema
# generate.go に sql/upsert を追加したら再度、schemaをgenerateしましょう
# 再generate すると `OnConflict` という関数が追加されます
$ go generate ./...

Create(データをinsert)する場合

// client: *ent.Client 
// User:   *ent.UserClient ← schema.go の内容によって User の部分は名前が変わります
// ↑ この2つは schema.go の内容によって自動に生成されます

u, err := client.User.
    Create().
    SetUserID("A0001")
    SetName("user1").
    SetAge(20).
    Save(ctx)

ent docs: エンティティを作成する

特定のやつをUpdateする場合

u, err := client.User.
    UpdateOneID(id).
    SetUserID("A0001")
    SetName("user1").
    SetAge(24).
    Save(ctx)
// or
u, err := client.User.
    Update().
    Where(user.UserID("A0001")).
    SetName("user1")
    SetAge(24).
    Save(ctx)

ent docs: ID指定で更新する

Upsertを実装する場合

  • この書き方はPostgresでは動作しません(SQLiteでは動作します)
  • conflictが起きた際はUpdateを行うといった処理の書き方になります
id, err := client.User.
    Create().
    SetUserID("A0001")  // UserIDがUnique指定されている場合
    SetAge(24).
    SetName("user1").
    OnConflict(). // 何かしらのconflictが発生したらUpdateを行う
    UpdateNewValues().
    ID(ctx)
  • Postgresで動作する様に修正します
  • Postgresの場合はconflictが起きる可能性のあるカラムを事前に明記しておく必要がありますOnConflictの引数に指定してあげましょう
    ただ、この書き方ではdefaultで作成されるcreated_atupdated_atの両方とも更新されてしまいます
id, err := client.User.
    Create().
    SetUserID("A0001") // UserIDがUnique指定されている場合
    SetName("user1").
    SetAge(24).
-   OnConflict().
+   OnConflict(
+       sql.ConflictColumns(user.FieldUserID),
+   ).
    UpdateNewValues().
    ID(ctx)
  • Insertされる場合はcreated_atが作成されて、Updateされる場合はcreated_atが更新されない様にします
id, err := client.User.
    Create().
    SetUserID("A0001")
    SetName("user1").
    SetAge(24).
    OnConflict(
        sql.ConflictColumns(user.FieldUserID),
    ).
-   UpdateNewValues().
+   Update(func(u *ent.UserUpsert) {
+       u.SetUserID("A0001")
+       u.SetName("user1")
+       u.SetAge("24")
+       u.UpdateUpdatedAt()
+   }).
    ID(ctx)

ent docs: 1つをUpsert​

まとめ

  • entを使ってUpsertを実装する場合、conflictが発生するカラムはOnConflictの中で指定した方がいいでしょう
    • SQLiteやPostgresなど複数の環境で同じコードを使えます
OnConflict(
    sql.ConflictColumns(user.FieldUserID),
).
  • Updateしたくないカラムがあれば、Updateしたいカラムだけ指定しましょう
Update(func(u *ent.UserUpsert) {
    u.SetUserID("A0001")
    u.SetName("user1")
    u.SetAge("24")
    u.UpdateUpdatedAt()
}).

参考

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4