web-dev-qa-db-ja.com

正しいSequelizeでENUMをドロップして作成しますか?

マイグレーションでPostgresのSequelizeを使用してENUMタイプを正しくドロップしてから再作成する方法は?たとえば、この移行ではenum_Users_status enum ...が削除されないため、status値を一度作成した後で再作成/変更しようとすると失敗します。

module.exports = {
    up: function (queryInterface, DataTypes) {
        queryInterface.createTable('Users', {
            //...
            status: {
                type: DataTypes.ENUM,
                values: [
                    'online',
                    'offline',
                ],
                defaultValue: 'online'
            }
            //...
        })
    },

    down: function (queryInterface) {
        queryInterface.dropTable('Users')
    },
}

結局、down内の列挙型をなんとか削除できましたが、up移行(これは、このstatus列挙を最初から作成することになっています)が失敗し、public.enum_Users_status列挙型が存在しません。

10
ZenDD

更新:これまでに3つのプロジェクトで使用したため、npmモジュールを作成することにしました: https://www.npmjs.com/package/replace-enum-postgresql

私はこれを行うユーティリティを作成しました。

utils/replace_enum.js

'use strict';

/**
 * Since PostgreSQL still does not support remove values from an ENUM,
 * the workaround is to create a new ENUM with the new values and use it
 * to replace the other.
 *
 * @param {String} tableName
 * @param {String} columnName
 * @param {String} defaultValue
 * @param {Array}  newValues
 * @param {Object} queryInterface
 * @param {String} enumName - Optional.
 *
 * @return {Promise}
 */
module.exports = function replaceEnum({
  tableName,
  columnName,
  defaultValue,
  newValues,
  queryInterface,
  enumName = `enum_${tableName}_${columnName}`
}) {
  const newEnumName = `${enumName}_new`;

  return queryInterface.sequelize.transaction((t) => {
    // Create a copy of the type
    return queryInterface.sequelize.query(`
      CREATE TYPE ${newEnumName}
        AS ENUM ('${newValues.join('\', \'')}')
    `, { transaction: t })
      // Drop default value (ALTER COLUMN cannot cast default values)
      .then(() => queryInterface.sequelize.query(`
        ALTER TABLE ${tableName}
          ALTER COLUMN ${columnName}
            DROP DEFAULT
      `, { transaction: t }))
      // Change column type to the new ENUM TYPE
      .then(() => queryInterface.sequelize.query(`
        ALTER TABLE ${tableName}
          ALTER COLUMN ${columnName}
            TYPE ${newEnumName}
            USING (${columnName}::text::${newEnumName})
      `, { transaction: t }))
      // Drop old ENUM
      .then(() => queryInterface.sequelize.query(`
        DROP TYPE ${enumName}
      `, { transaction: t }))
      // Rename new ENUM name
      .then(() => queryInterface.sequelize.query(`
        ALTER TYPE ${newEnumName}
          RENAME TO ${enumName}
      `, { transaction: t }))
      .then(() => queryInterface.sequelize.query(`
        ALTER TABLE ${tableName}
          ALTER COLUMN ${columnName}
            SET DEFAULT '${defaultValue}'::${enumName}
      `, { transaction: t }));
  });
}

これは私の移行です:

'use strict';

const replaceEnum = require('./utils/replace_enum');

module.exports = {
  up: (queryInterface, Sequelize) => {
    return replaceEnum({
      tableName: 'invoices',
      columnName: 'state',
      enumName: 'enum_invoices_state',
      defaultValue: 'created',
      newValues: ['archived', 'created', 'paid'],
      queryInterface
    });
  },

  down: (queryInterface, Sequelize) => {
    return replaceEnum({
      tableName: 'invoices',
      columnName: 'state',
      enumName: 'enum_invoices_state',
      defaultValue: 'draft',
      newValues: ['archived', 'draft', 'paid', 'sent'],
      queryInterface
    });
  }
};
8
Abel Osorio

データを失うことなくタイプ列挙型を変更/編集したい場合。これが私の移行コードです。うまくいけば、それが役立ちます。

queryInterface.changeColumn(
  'table_name',
  'Column_name',
  {
    type: Sequelize.TEXT,
  },
),
queryInterface.sequelize.query('drop type enum_tableName_columnName;')
.then(() => queryInterface.changeColumn(
  'table_name',
  'column_name',
  {
    type: Sequelize.ENUM('value1','value2'),
  },
)),
5
shakir ullah

ENUMを手動でdownにドロップすると、かなりうまくいきました。

module.exports = {
    up: function (queryInterface, DataTypes) {
        queryInterface.createTable('Users', {
            //...
            status: {
                type: DataTypes.ENUM,
                values: [
                    'online',
                    'offline',
                ],
                defaultValue: 'online'
            }
            //...
        })
    },

    down: function (queryInterface) {
        return queryInterface.sequelize.transaction(t => {
            return Promise.all([
                queryInterface.dropTable('Users'),
                queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_Users_status";'),
            ]);
        });
    }
};
4
goud1it

Shakir ullahの投稿と githubへのコメント について詳しく説明します。

module.exports = {
  up: (queryInterface, Sequelize) => {
    // 1. Change the type of the column to string
    return queryInterface.changeColumn('Users', 'status', {
      type: Sequelize.STRING,
    })
    // 2. Drop the enum
    .then(() => {
      const pgEnumDropQuery = queryInterface.QueryGenerator.pgEnumDrop('Users', 'status');
      return queryInterface.sequelize.query(pgEnumDropQuery);
    })
    // 3. Create the enum with the new values
    .then(() => {
      return queryInterface.changeColumn('Users', 'status', {
        type: Sequelize.ENUM,
        values: [
          'online',
          'offline',
        ],
        defaultValue: 'online'
      });
    })
  },

  // Here I made the choice to restore older values but it might not work
  // if rows were inserted with the new enum.
  // What you want to do then is up to you. Maybe lose the enum and keep
  // the column as a string.
  down: (queryInterface, Sequelize) => {
    // Do as above to restore older enum values
    return queryInterface.changeColumn('Users', 'status', {
      type: Sequelize.STRING,
    }).then(() => {
      const pgEnumDropQuery = queryInterface.QueryGenerator.pgEnumDrop('Users', 'status');
      return queryInterface.sequelize.query(pgEnumDropQuery);
    }).then(() => {
      return queryInterface.changeColumn('Users', 'status', {
        type: Sequelize.ENUM,
        values: [
          'older',
          'values',
        ],
        defaultValue: 'older'
      });
    })
  },
}
1
7hibault