🍣

PostgreSQL の演算子「?」が GORM のプレースホルダとバッティングするんだが

2022/01/13に公開

概要

Go 言語の ORM ライブラリであるところの Gorm を利用していて、? をプレースホルダでなくて PostgreSQL の ?(josonb のキーが存在するかの演算子 )として使いたかったのですが、プレースホルダとバッティングしてしまって迷ったのでメモ。(?のググラビリティよ)

なにが困りますか?

user テーブルに attributes というカラムがあって、これが JSON を格納する jsonb である時に、JSON のキーとして role があるものを取得したいとします。クエリにすると以下のようにかけます。

SELECT * FROM "user" WHERE "attributes"::jsonb ? 'role'

これを Gorm で表現しようとしてみましょう。

db.Find(&user, "attributes ? 'role'")

これは OK です。では、role 以外の値でも対応できるような関数を作ろうとするとどうなるでしょうか? 変数 attrrole とかキーの名前が入ってくると思うと以下のようになると思います。

db.Find(&user, "attributes ? ?", attr)

しかし、これはうまくいきません。変数を受け取るプレースホルダの ? と PostgreSQL の演算子の ? がバッティングしてしまいます。 バックスラッシュでエスケープできるだろうと思った方、不正解です(私です)。

どうすればいいか?

gorm.io/datatypes パッケージを使う

Gorm のドキュメントにその存在が記されています。

db.Find(&user, datatypes.JSONQuery("attributes").HasKey(attr))

? の代わりに jsonb_exsists() を利用する

実は PostgreSQL には ? と同等の関数が用意されています。これを利用すれば問題なくいけます。

db.Find(&user, "jsonb_exsists(attributes, ?)", attr)

こういった PostgreSQL の演算子には対応する関数がちゃんと用意されていて、その一覧は pg_operator というテーブルに格納されているそうです。なるほどですね・・・。

SELECT oprname, oprcode FROM pg_operator;
 oprname |          oprcode
---------+---------------------------
 =       | int48eq
 <>      | int48ne
 <       | int48lt
 >       | int48gt
 <=      | int48le
 >=      | int48ge
 ...

「わかるか!」「しらんがな!」という心の声が漏れてしまいましたが、わかってしまえばどうということはないですね。

Happy hacking!

Discussion