マイグレーションで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
列挙型が存在しません。
私はこれを行うユーティリティを作成しました。
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
});
}
};
データを失うことなくタイプ列挙型を変更/編集したい場合。これが私の移行コードです。うまくいけば、それが役立ちます。
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'),
},
)),
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";'),
]);
});
}
};
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'
});
})
},
}