ENUM値のラベル名変更

# はじめに
にゃーん
この記事は、[PostgreSQL 10全部ぬこ Advent Calendar 2017](https://qiita.com/advent-calendar/2017/pg10-nuko-only) の3日目のエントリです。

# ENUMとは
ENUMとはENUMERATION(列挙)型は値域を規定する一種のデータタイプ。例えば、神奈川県の市という内容のkanagawa_cityという列挙型を定義してみる。

```postgres-console
enum=#  CREATE TYPE kanagawa_city AS ENUM ('横浜','川崎','相模原','町田','横須賀');
CREATE TYPE
enum=#
```

そしてこのkanagawa_city型の列を含むテーブルを作成する。

```postgres-console
enum=# CREATE TABLE test (id int, city kanagawa_city);
CREATE TABLE
enum=# \d test
                   Table "public.test"
 Column |     Type      | Collation | Nullable | Default
--------+---------------+-----------+----------+---------
 id     | integer       |           |          |
 city   | kanagawa_city |           |          |
```
このtestテーブルのcity列にENUMで列挙した値域以外を設定すると怒られる。
（横浜や町田はkanagawa_cityの値域なので挿入や更新ができるが、立川や八王子はkanagawa_cityの値域ではないのでエラーになる）

```postgres-console
enum=# INSERT INTO test VALUES (1, '横浜');
INSERT 0 1
enum=# INSERT INTO test VALUES (2, '立川');
ERROR:  invalid input value for enum kanagawa_city: "立川"
LINE 1: INSERT INTO test VALUES (2, '立川');
                                    ^
enum=# UPDATE test SET city = '町田';
UPDATE 1
enum=# UPDATE test SET city = '八王子';
ERROR:  invalid input value for enum kanagawa_city: "八王子"
LINE 1: UPDATE test SET city = '八王子';
```

データベース側で列値を規定するという機能だが、「SQLアンチパターン」の「31のフレーバー」の章でも挙げられているように、利用時にはちょいと検討が必要な機能でもある。

# 参考：PostgreSQLでの管理方法
PostgreSQLではENUMをpg_enumというシステムカタログで管理している。
例えば、さっきのkanagawa_cityという列挙型は以下のようにpg_enumに格納されている。

```postgres-console
enum=# TABLE pg_enum;
 enumtypid | enumsortorder | enumlabel 
-----------+---------------+-----------
     16396 |             1 | 横浜
     16396 |             2 | 川崎
     16396 |             3 | 相模原
     16396 |             4 | 町田
     16396 |             5 | 横須賀
(5 rows)
```
enumsortorder に注意。ソートするときにはここの値でソートされ、enumlabel の値ではソートされない。

# ENUM値の変更（PostgreSQL 9.6まで）
さて、CREATE TYPEで作成したENUMの内容を変更したい、というケースもあるかもしれない。

* 列挙値を追加したい
* 列挙値を削除したい
* 列挙値のラベルを変更したい。

PostgreSQL 9.6までは列挙値の追加のみ対応していた。

```postgres-console
enum=#  CREATE TYPE kanagawa_city AS ENUM ('横浜','川崎','相模原','横須賀');
CREATE TYPE
```

kanagawa_city型の値域として、町田を追加する。

```postgres-console
enum=# ALTER TYPE kanagawa_city ADD VALUE '町田';
ALTER TYPE
enum=# TABLE pg_enum;
 enumtypid | enumsortorder | enumlabel 
-----------+---------------+-----------
     41868 |             1 | 横浜
     41868 |             2 | 川崎
     41868 |             3 | 相模原
     41868 |             4 | 横須賀
     41868 |             5 | 町田
(5 rows)
```
町田がkanagawa_cityに追加された。町田は神奈川。

なお、PostgreSQL 9.6までは、一度追加したラベルの内容を変更することはできない。
また、ラベルの削除もできない。これがENUMは変更に弱い、という問題に繋がると。
もしラベルを変更したければ、

* この列挙型を使っている全テーブルをCOPY等でEXPORT、
* この列挙型を使っている全テーブルをDROP
* DROP TYPE ... CASCADEで列挙型を削除
* ラベルを変更した列挙型をCREATE TYPEで再生成
* この列挙型を使っていた全テーブルをCREATE TABLEで再生性
* EXPORTしたデータを再ロード

という手順が必要になり、ちょっと実運用ではやってられんよなあと。

# PostgreSQL 10での改善
PostgreSQL 10では、列挙型に追加ずみのラベルを変更可能になった。

現在のkanagawa_city型の列挙値はこんな感じ。

```postgres-console
enum=# TABLE pg_enum;
 enumtypid | enumsortorder | enumlabel 
-----------+---------------+-----------
     41868 |             1 | 横浜
     41868 |             2 | 川崎
     41868 |             3 | 相模原
     41868 |             4 | 横須賀
     41868 |             5 | 町田
(5 rows)
```

で、この列挙型を使った列を定義して、データを格納する。

```postgres-console
enum=# CREATE TABLE test (id int primary key, data kanagawa_city);
CREATE TABLE
enum=# INSERT INTO test VALUES (1, '横浜');
INSERT 0 1
enum=# TABLE test;
 id | data 
----+------
  1 | 横浜
(1 row)
```
ここで、「横浜」というラベルを「横濱」に変更する。

```postgres-console
enum=# ALTER TYPE kanagawa_city RENAME VALUE '横浜' TO '横濱';
ALTER TYPE
enum=# TABLE test;
 id | data 
----+------
  1 | 横濱
(1 row)
```

ALTER TYPEの終了後にtestテーブルを参照すると、「横浜」は「横濱」に変更されている。

# おわりに
町田は神奈川。

PostgreSQL 10ではENUMのラベル変更を大きなコストをかけずに実施できるようになった。
これでENUMの「変更に弱い」という問題点の一部は解決できそう。
残念ながらラベルの削除は未対応。つか、既に使用されているラベルの削除したらどうするんだ、みたいな仕様上の問題の整理から必要なんだろうなあ。

# 参考：該当するリリースノート
本エントリに関連するPostgreSQL 10リリースノートの記載です。

E.2.3.5. Data Types

> * Allow ENUM values to be renamed (Dagfinn Ilmari Mannsaker)