entでUpsertを実装する（for PostgreSQL, SQLite3）

# はじめに
<!-- 発端や概要を記載 -->
- 仕事でORMに[ent](https://github.com/ent/ent)を使いAPIを作成しているんですが、Upsertを実装する際にPostgresだとつまずいた部分があったため、Tipsになればと良いなと思いやったことを書き残していきます

# 発生した問題
- Upsertを実装したものの
    - SQLiteでは正常に動作する
    - PostgreSQLでは下の様なエラーが発生する
- CreatedAtは更新したくないのに更新されてしまう

```sh
ERROR: ON CONFLICT DO UPDATE requires inference specification or constraint name (SQLSTATE 42601)
```

今、こんな問題が起きてる方は 5. から見ると良いと思います:+1:
5. [Upsertを実装する場合](#upsertを実装する場合)

# 目次
1. [ent v0.9.0からUpsertが利用できる様になりました](#ent-v090からupsertが利用できる様になりました)
1. [generate.goにsql/upsert機能フラグを追加します](#generategoに-sqlupsert-機能フラグを追加します)
1. [Create(データをinsert)する場合](#createデータをinsertする場合)
1. [特定のやつをUpdateする場合](#特定のやつをupdateする場合)
1. [Upsertを実装する場合](#upsertを実装する場合)
1. [まとめ](#まとめ)
1. [参考](#参考)

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

https://entgo.io/ja/blog/2021/08/05/announcing-upsert-api/

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

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

<a id="#Chapter2"></a>
# generate.goに sql/upsert 機能フラグを追加します
```diff_go:/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
```

```sh
# generate.go に sql/upsert を追加したら再度、schemaをgenerateしましょう
# 再generate すると `OnConflict` という関数が追加されます
$ go generate ./...
```
<a id="#Chapter3"></a>
# Create(データをinsert)する場合
```go
// 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: エンティティを作成する
https://entgo.io/ja/docs/crud#%E3%82%A8%E3%83%B3%E3%83%86%E3%82%A3%E3%83%86%E3%82%A3%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B

<a id="#Chapter4"></a>
# 特定のやつをUpdateする場合
```go
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指定で更新する
https://entgo.io/ja/docs/crud#id%E6%8C%87%E5%AE%9A%E3%81%A7%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B

<a id="#Chapter5"></a>
# Upsertを実装する場合

- この書き方はPostgresでは動作しません（SQLiteでは動作します）
- conflictが起きた際はUpdateを行うといった処理の書き方になります  


```go
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`の両方とも更新されてしまいます

```diff_go
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`が更新されない様にします


```diff_go
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​
https://entgo.io/ja/docs/crud#1%E3%81%A4%E3%82%92upsert

# まとめ
- entを使ってUpsertを実装する場合、conflictが発生するカラムは`OnConflict`の中で指定した方がいいでしょう
    - SQLiteやPostgresなど複数の環境で同じコードを使えます

```go
OnConflict(
    sql.ConflictColumns(user.FieldUserID),
).
```

- Updateしたくないカラムがあれば、Updateしたいカラムだけ指定しましょう

```go
Update(func(u *ent.UserUpsert) {
    u.SetUserID("A0001")
    u.SetName("user1")
   	u.SetAge("24")
    u.UpdateUpdatedAt()
}).
```



# 参考
- [バージョン0.9.0でUpsert APIを追加しました！](https://entgo.io/ja/blog/2021/08/05/announcing-upsert-api/)
- [CRUD API - ent.io](https://entgo.io/ja/docs/crud/)