PostgreSQL は SQL 文をケースインセンシティブに解釈する。TypeORM での調整方法

TypeORM と PostgreSQL の組み合わせで軽くつまづいた…。

目次

経緯

TypeORM を作って、次のようなエンティティを定義した。

@Entity()
export default class MyCustomer {
  @PrimaryColumn({ type: 'varchar' })
  userName: string;
}

コレを PostgreSQL に対して Sync すると、次のようなテーブルとカラムが自動生成される。

項目 名前 ケース
テーブル名 my_customer スネークケース
カラム名 userName キャメルケース

…ケースが統一されていなくて違和感があるが、まぁ良いだろう。テーブルのことを考えたくなくて Sync したんだし。

psql で問題発生

そう思ったのも束の間、テストのために psql で PostgreSQL に繋いで UPDATE 文を書いてみると、なぜかうまくいかなかった。

admin=# SELECT userName from my_customer;

ERROR:  column "userName" does not exist

そんなカラム存在しないよ、だと…?

SELECT * FROM my_customer; と叩けばちゃんと userName カラムが見えているのに、何故だ…。

PostgreSQL はケースインセンシティブ

そこで調べてみると、PostgreSQL は SQL 文をケースインセンシティブ、つまり大文字・小文字の区別をせずに解釈することが分かった。

内部的には大文字・小文字を区別して userName カラムを作っているけど、SQL 文の中で userName と書いても、username と解釈されてしまうワケだ。

回避策 : ダブルクォートで囲む

回避策はあって、

SELECT "userName" from my_customer;

このようにカラム名やテーブル名をダブルクォートで囲むことで、大文字・小文字を区別して認識させられる。

# テーブル名も囲んだりして UPDATE する例
UPDATE "AdminUsers"
SET "userName" = 'John Doe'
WHERE "userId" = 1;

TypeORM が付ける名前を修正する

TypeORM が内部的に生成する SQL 文にはダブルクォートが書かれるようなので、TypeORM だけ使う上ではコレでも別に問題にならない。

しかし、自分で PostgreSQL を直で触ったりする時に面倒臭いし、何よりテーブル名とカラム名でケースに統一感がないのもキモいので、調整することにする。

一番手っ取り早いのは、デコレータ内で名前を渡してやること。

// ↓ 実際のテーブル名は複数形に
@Entity('my_customers')
export default class MyCustomer {
  // ↓ name プロパティで実際のカラム名をスネークケースに
  @PrimaryColumn({ name: 'user_name', type: 'varchar' })
  userName: string;
}

こんな感じで指定できる。

以下の記事で紹介されているように、名前付けルールを自分で調整し、pluralize パッケージを使って複数形を導いて自動適用したりもできる。

まとめ

TypeORM に限らず、O/R マッパーを使う際は、バックエンドとなる DB の仕様もちゃんと意識しておかないと、やっぱりコケるところがありますね…。