GoのORMのSQLBoilerでDuplicateEntryのエラーハンドリングする方法

こんにちは!株式会社Voicyでバックエンドエンジニアをしているたーふーと申します。
簡単に自己紹介をさせていただくと、自分は去年の9月にVoicyに入社したので、入社してもうすこしで1年くらいになります!

今回はGoのORMであるSQLBoilerでDuplicateEntryのエラーハンドリングする方法についてお話しできればと思います!

自分自身Go歴は浅いのですが、 その中でSQLBoiler関連の情報が少ないように感じた経緯があり、今回この記事を書かせていただきます。

なので同じような方の参考になれば幸いです!もし興味があれば是非ご覧ください!!

SQLBoilerとは?

まずそもそもSQLBoilerとは何か説明します。

SQLBoilerとは名前から想像できると思いますが、Goで使われるORMライブラリの1つです。
Goでは他にも有名なGORMなどのORMがありますが、VoicyではSQLBoilerを採用しています!

Go初心者でGoのORM関連について気になる方は以下の記事が参考になったので、見てみてください!

zenn.dev

またSQLBoilerの特徴としては以下が挙げられています。

  • DBのスキーマを読み取り、コードを自動生成する
  • 静的型付けのため動作が高速

※参考 github.com

ちなみに以前下記記事も取り上げているので、もしご興味があれば!

tech-blog.voicy.jp

DuplicateEntryとは?

その名の通りこちらはデータの複製に当たります。 そのエラーということなのでダブりのエラーのようなものになります。

mysqlなどのDBでユニーク制約をすると基本的に指定したカラムの重複は許容されなくなります。 その場合に重複したデータをインサートしようとすると返されるエラーがこのエラーとなっています。

重複を許容しないものを扱う場合はこれが簡単にハンドリングできると実装もスムーズに進められるかと思います。

SQLBoilerでハンドリングする際の問題点

例えばSQLBoilerでinsertをするとORM側のエラーが返ってきます。 そこでSQLBoilerはDuplicateEntryの場合でもormで別の情報もwrapした形でエラーを返してきます。

err := user.InsertG(ctx, boil.Infer())

なので単にきたエラーをハンドリングするのは難しく、wrapを解除して元のエラーを探さなくてはいけません。 しかしerrorsのerrors.Unwrap()などを利用してもうまく解除できず、どのように元のerrorを取得するのかが問題点となりました。

解決方法

結論SQLBoilerがerrorのwrapに利用しているpackageを利用することで解決できました! 利用していたpackageは以下です。

github.com/friendsofgo/errors

また以下のように実装をすることできちんと想定した動きにすることができました!

err := user.InsertG(ctx, boil.Infer())
if err != nil {
  // error のwrapを解く
  e := errors2.Cause(err)
  //Duplicate entryを判定する
  if mysqlErr, ok := e.(*mysql.MySQLError); ok {
    fmt.Println("ダブっとるで")
  }
}

終わりに

最後まで記事をご覧いただきありがとうございます!
この記事が少しでも参考になれば幸いです!