はじめに
- 仕事でORMにentを使いAPIを作成しているんですが、Upsertを実装する際にPostgresだとつまずいた部分があったため、Tipsになればと良いなと思いやったことを書き残していきます
発生した問題
- Upsertを実装したものの
- SQLiteでは正常に動作する
- PostgreSQLでは下の様なエラーが発生する
- CreatedAtは更新したくないのに更新されてしまう
ERROR: ON CONFLICT DO UPDATE requires inference specification or constraint name (SQLSTATE 42601)
今、こんな問題が起きてる方は 5. から見ると良いと思います
5. Upsertを実装する場合
目次
- ent v0.9.0からUpsertが利用できる様になりました
- generate.goにsql/upsert機能フラグを追加します
- Create(データをinsert)する場合
- 特定のやつをUpdateする場合
- Upsertを実装する場合
- まとめ
- 参考
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_at
やupdated_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()
}).