Sequelize でユニークキーを設定する方法 (PostgreSQL)

#はじめに

Sequelize でユニークキーを設定する方法についてウェブに沢山出てくるものの、
意図したとおりに動作しないものが多数あってハマったため、
上手くいった方法を残しておきます。(2019/6/13)

#ユニークキーを設定する

該当フィールドに `unique: true` と記述すれば可能です。
よくあるユーザ登録時に重複したメールアドレス登録させないとかで設定する場合は、
下記のようなコードになります。

```javascript:20190416081113-create-user.js
module.exports = {
  up: (queryInterface, Sequelize) => queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";')
    .then(() => queryInterface.createTable('users', {
      id: {
        allowNull: false,
        primaryKey: true,
        defaultValue: Sequelize.literal('uuid_generate_v4()'),
        type: Sequelize.UUID,
      },
      name: {
        allowNull: false,
        type: Sequelize.STRING
      },
      email: {
        allowNull: false,
        type: Sequelize.STRING,
        unique: true, // unique パラメータに true を設定すれば OK
      },
      password: {
        allowNull: false,
        type: Sequelize.STRING,
      },
      created_at: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
      },
      updated_at: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
      	onUpdate : Sequelize.literal('CURRENT_TIMESTAMP'),
      }
    })),
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('users');
  }
};
```

`sequelize db:migrate` を実行した後に、
実際に SQL を叩いて index が作成されていることを確認します。

```sql
test_unique=#SELECT tablename, indexname FROM pg_indexes WHERE indexname='users_email_key';
 tablename |    indexname    
-----------+-----------------
 users     | users_email_key
(1 row)

test_unique=# \d users_email_key
           Index "public.users_email_key"
 Column |          Type          | Key? | Definition 
--------+------------------------+------+------------
 email  | character varying(255) | yes  | email
unique, btree, for table "public.users"
```

#複合ユニークキーを設定する

マイグレーションファイルに出力される `queryInterface.createTable` の第3引数には option を指定することが出来、そこに uniqueKeys の項目を追加することで複合ユニークキーを記述することが可能です。
中間テーブルで重複した ID の組み合わせを排除するパターンでの使用は下記になります。

```javascript:20190507060929-create-users-workspaces.js
'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('users_workspaces', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      user_id: {
        allowNull: false,
        type: Sequelize.UUID,
      },
      workspace_id: {
        allowNull: false,
        type: Sequelize.UUID,
      },
      created_at: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
      },
      updated_at: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
      	onUpdate : Sequelize.literal('CURRENT_TIMESTAMP'),
      }
    }, {
      // ここで複合ユニークキーの情報を記載する
      // fields の部分に複合ユニークキーとして設定したいフィールドを含める
      // ２つ以上設定することも可
      uniqueKeys: {
          UsersWorkspacesIndex: {
              fields: ['user_id', 'workspace_id']
          }
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('users_workspaces');
  }
};
```

`sequelize db:migrate` を実行した後に、
実際に SQL を叩いて index が作成されていることを確認します。

```sql
test_unique=# SELECT tablename, indexname FROM pg_indexes WHERE indexname='users_workspaces_user_id_workspace_id_key';
    tablename     |                 indexname                 
------------------+-------------------------------------------
 users_workspaces | users_workspaces_user_id_workspace_id_key
(1 row)

test_unique=# \d users_workspaces_user_id_workspace_id_key
Index "public.users_workspaces_user_id_workspace_id_key"
    Column    | Type | Key? |  Definition  
--------------+------+------+--------------
 user_id      | uuid | yes  | user_id
 workspace_id | uuid | yes  | workspace_id
unique, btree, for table "public.users_workspaces"
```

#おわりに

Sequelize のみならず CLI でデータベースのマイグレーション作業を行う場合は、
意図通りのインデックスが貼られるかテストしながら作業進めたほうがいいなと感じました。

#参考リンク

https://github.com/sequelize/sequelize/pull/9946/files
https://github.com/sequelize/sequelize/issues/9041