これは SmartHR Advent Calendar 2024 シリーズ2 の2日目の記事です。
ちょっと前にツイートしたのがまあまあ見られたみたいなので、ちゃんと書いてみる(汚い言葉は良くないね)。
バーカバーカ! pic.twitter.com/lSWwo8AhK1
— とみたまさひろ🍣🍺 (@tmtms) 2024年11月20日
macOS で次のテキストファイルを sort コマンドでソートするとデタラメに並ぶんですよ。
千葉県千葉市 千葉県市川市 千葉県柏市 東京都北区 東京都千代田区 東京都大田区 東京都江戸川区 東京都江東区 東京都港区
$ sort a.txt 千葉県柏市 東京都北区 東京都港区 千葉県千葉市 千葉県市川市 東京都大田区 東京都江東区 東京都千代田区 東京都江戸川区
文字のコードを無視して文字数だけで並んでるように見える。使い物にならない。macOS アホすぎる。
原因は /usr/share/locale/ja_JP.UTF-8/LC_COLLATE
ファイルが la_LN.US-ASCII/LC_COLLATE
にリンクされてるからっぽい。
$ cd /usr/share/locale/ $ ls -l ja_JP.UTF-8/LC_COLLATE lrwxr-xr-x 1 root wheel 28 10 15 20:22 ja_JP.UTF-8/LC_COLLATE -> ../la_LN.US-ASCII/LC_COLLATE
LC_COLLATE
は文字の照合規則を決めるもので、それが ASCII 文字のためのファイルを指してるということで、ASCII 文字以外は全部同値としてみなしてるということらしい。
つまりこういうデータをソートしてるのと同じ。そりゃ文字長順に並ぶわな。同じ文字とみなされた場合は文字コードの順に並ぶみたいだけども。
������ ������ ����� ����� ������� ������ ������� ������ �����
なお LC_COLLATE=C
を指定することで単純なバイト列としてソートされるので、文字コード順にソートされる。
$ LC_COLLATE=C sort a.txt 千葉県千葉市 千葉県市川市 千葉県柏市 東京都北区 東京都千代田区 東京都大田区 東京都江戸川区 東京都江東区 東京都港区
もともとこれに気がついたのは sort コマンドではなくて PostgreSQL の ORDER BY
でのソートがおかしくなったからだった。
会社の開発用の PC は MacBook なんだけど、PostgreSQL は Docker を使って Linux 上で動かしてる。そのせいでかなり遅いんで、Docker 使わずに macOS 上で直接動かそうとしたらソート順がおかしくなった。
PostgreSQL のロケールは何もしないと libc が使われるので、OS 環境に依存する。
$ initdb -U postgres /tmp/hoge
postgres=# \x 拡張表示は on です。 postgres=# \l postgres データベース一覧 -[ RECORD 1 ]--------+---------------- 名前 | postgres 所有者 | masahiro.tomita エンコーディング | UTF8 ロケールプロバイダー | libc 照合順序 | ja_JP.UTF-8 Ctype(変換演算子) | ja_JP.UTF-8 ICUロケール | ICUルール: | アクセス権限 |
この状態で ORDER BY すると OS と同じアホな子になってることがわかる。
postgres=# create table test (c varchar); CREATE TABLE postgres=# insert into test (c) values ('あああああ'),('いいいい'),('ううう'),('ええ'),('お'); INSERT 0 5 postgres=# select * from test; c ------------ あああああ いいいい ううう ええ お (5 行) postgres=# select * from test order by c; c ------------ お ええ ううう いいいい あああああ (5 行)
RDB の Collation による妙な挙動で MySQL の寿司ビール問題を思い出したわ。
あれ? MySQL の utf8mb4 charset って、4バイト文字同士を比較すると同じ文字扱いされる?
— とみたまさひろ🍣🍺 (@tmtms) 2014年12月22日
SELECT '🍣'='🍺' → 1
MySQL的には寿司とビールは同じ扱い。
sort でやったのと同じように initidb 時に --lc-collate=C
を指定したらうまくいった。
$ initdb -U postgres --lc-collate=C /tmp/hoge
postgres=# \l postgres データベース一覧 -[ RECORD 1 ]--------+------------ 名前 | postgres 所有者 | postgres エンコーディング | UTF8 ロケールプロバイダー | libc 照合順序 | C Ctype(変換演算子) | ja_JP.UTF-8 ICUロケール | ICUルール: | アクセス権限 | postgres=# select * from test order by c; c ------------ あああああ いいいい ううう ええ お (5 行)
しかし、エンコーディングがUTF-8 なのに COLLATE が C というのはどうなんだろうなー…と思って、PostgreSQL は libc ロケール以外にも ICU ロケールが使えるので、それを指定することにした。
$ initdb -U postgres --locale-provider=icu --icu-locale=ja_JP.UTF-8 /tmp/hoge
postgres=# \l postgres データベース一覧 -[ RECORD 1 ]--------+------------ 名前 | postgres 所有者 | postgres エンコーディング | UTF8 ロケールプロバイダー | icu 照合順序 | ja_JP.UTF-8 Ctype(変換演算子) | ja_JP.UTF-8 ICUロケール | ja-JP ICUルール: | アクセス権限 | postgres=# select * from test order by c; c ------------ あああああ いいいい ううう ええ お (5 行)
しかし macOS は UI が使いにくいとは思っていたが OS としてもダメだったとはなぁ。 いやまあ macOS なんて使わずに Docker コンテナ使えばまともな Linux が使えるんだけど、あまりにも遅いんで…。
ていうか、開発にデスクトップ Linux 使いたい。