正确删除并使用 sequelize 创建 ENUM?

Posted

技术标签:

【中文标题】正确删除并使用 sequelize 创建 ENUM?【英文标题】:Drop and create ENUM with sequelize correctly? 【发布时间】:2018-01-08 07:50:20 【问题描述】:

如何在迁移中为 Postgres 正确删除然后重新创建 ENUM 类型?例如,此迁移不会删除 enum_Users_status 枚举...因此,任何在创建/更改 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 枚举类型没有存在..

【问题讨论】:

【参考方案1】:

更新:到目前为止,我已经在三个项目中使用了它,所以我决定创建一个 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
    );
  
;

【讨论】:

如何添加没有 defaultValue 的枚举? @Vaulstein 你可以省略defaultValue 选项。 嘿@Abel 试过了,没用。将分享错误信息【参考方案2】:

如果您想更改/编辑类型枚举而不丢失数据。这是我的迁移代码。希望对您有所帮助。

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'),
  ,
)),

【讨论】:

如果您有区分大小写的表/列名称,请记住在枚举名称周围使用“” - 否则您将收到 type does not exist 错误。【参考方案3】:

down 中手动删除 ENUM 对我来说效果很好。

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】:

详细说明 shakir ullah 的帖子和 comment on 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'
      );
    )
  ,

【讨论】:

【参考方案5】:

这种方式对我有用:

module.exports = 
  up: async (queryInterface, Sequelize) => 
    await queryInterface.addColumn(
      'users',  
      'status', 
      
        type: Sequelize.ENUM,
        values: [
          'online',
          'offline'  
        ],
        defaultValue: 'online',
        allowNull: false,
      
    )   
  ,

  down: async (queryInterface) => 
    await queryInterface.removeColumn('users', 'status')
    .then(queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_users_status";'))
  
;

【讨论】:

以上是关于正确删除并使用 sequelize 创建 ENUM?的主要内容,如果未能解决你的问题,请参考以下文章

Sequelize .update() 不使用正确的位置并更新所有行

从已经定义的模型中获取 Sequelize.js ENUM 值

sequelize migration delete enum col and want that col back occur error

尝试使用 sequelize 创建迁移时出错

node-sequelize学习笔记二(创建删除表安全检查表表列的数据类型)

egg.js 24.8sequelize模型-新增