Rails 8.1: 生SQLやArelのブーリアン型カラムをTRUEやFALSEと比較できるようになった(翻訳)
私がRailsアプリケーションで何かする場合、ほぼすべてのクエリをActive Recordで書いています。
class Post
scope :published, -> { where(published: true) }
end
Post.published.to_sql
# => SELECT "posts".* FROM "posts" WHERE "posts"."published" = TRUE
しかしクエリが複雑になりすぎる場合は生SQLを手書きする必要が生じることもあります。生SQLを使うようになると、ORM(Object-Relational Mapping)を使うメリットのひとつである、アプリケーションのメンテナンスしやすさを維持するのが難しくなることがあります。
仮に上のコードが標準のActive Recordでは表現できず、何らかの生SQLを使う必要があるとします。MySQLの場合は以下のようになります。
Post.where("published = 1").to_sql
# => SELECT `posts`.* FROM `posts` WHERE published = 1
この例は、最初の例と2つの点で異なっています。
1つ目は、MySQLでは識別子をバッククォート(``
)で囲んでいること。
2つ目は(こちらの方が重要なのですが)、MySQLではpublished
をTRUE
ではなく1
と比較しています。
もしこの生SQLをPostgreSQLで実行したら、以下のエラーが発生します。
PG::UndefinedFunction: ERROR: operator does not exist: boolean = integer (ActiveRecord::StatementInvalid)
LINE 1: SELECT "posts".* FROM "posts" WHERE (published = 1) /*applic...
🔗 BOOLEAN
型の意味はRDBMSによって異なる可能性がある
MySQLでpublished = 1
という比較が動作する理由は、MySQLにはBOOLEAN
というカラム型が実際には存在しないためです。一応MySQLにはBOOL
やBOOLEAN
というエイリアスは存在しますが、内部ではTINYINT(1)
というカラム型を参照しています。
この点はSQLiteも同じなのですが、SQLiteではBOOLEAN
がINTEGER
型に対応付けられている点が異なります。
MySQLとSQLiteはどちらも内部でBOOLEAN
に整数型を利用しているため、published = 1
のようなブーリアン型カラムへのクエリが可能です。しかし残念なことに、PostgreSQLのBOOLEAN
型は本物の型です。つまり、PostgreSQLのBOOLEAN
型を比較するときは必ずTRUE
やFALSE
と比較しなければなりません。
ありがたいことに、TRUE
やFALSE
は実はMySQLやSQLiteでも使えます。BOOLEAN
がMySQLやSQLiteのinteger型のエイリアスになっているのと同様に、TRUE
は1
のエイリアスであり、FALSE
は0
のエイリアスになっています。つまり、PostgreSQL/MySQL/SQLiteという3つのデータベースすべてで機能するクエリを作成する場合は、BOOLEAN
型のカラムを比較するときに常にTRUE
とFALSE
を使う必要があります。
原注
SQLite 3.23.0まではTRUE
とFALSE
のエイリアスが追加されていなかったため、Active Record 8.0までは、古いバージョンとの互換性を保つためにSQLite用のクエリで1
と 0
を使っていました。ただし、Active Record 8.1以降ではSQLiteの最小サポートバージョンが3.23.0になるため(809abd3)、Active RecordはArel(34bebf3)と標準のActive Record(576db3b)の両方のクエリでTRUE
とFALSE
を利用可能になります。
1
と0
ではなくTRUE
とFALSE
を使うことには別のメリットもあります。
1
や0
を使っているクエリを読む場合は、そのカラム型がブール型か整数型かを適切に判断できるでしょうか?published
などのようにブール型らしさが伝わるカラム名ならまだしも、現実にはもっとわかりにくいカラム名である可能性もあります。カラムの比較にTRUE
やFALSE
を使っておけば、今後そのカラムがブール型であることをすぐに理解できるようになります。
🔗 互換性を失わないようにSQLクエリを書く
そういうわけで、生SQLでBOOLEAN
型のカラムを使う必要が生じたら、以下のようにブーリアンリテラル(TRUE
とFALSE
)を使うようにしましょう!
Post.where("published = TRUE").to_sql
# => SELECT "posts".* FROM "posts" WHERE published = TRUE
こうすることで、PostgreSQL/MySQL/SQLiteのどれでもSQLクエリの互換性が維持されるようになり、クエリのカラム型も明確になるので今後クエリが読みやすくなります。
概要
CC BY-NC-SA 4.0 International Deedに基づいて翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。