【问题标题】:Drop and create ENUM with sequelize correctly?正确删除并使用 sequelize 创建 ENUM?
【发布时间】: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 枚举类型没有存在..

【问题讨论】:

    标签: node.js postgresql migration sequelize.js


    【解决方案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";'))
        }
      };
      

      【讨论】:

        猜你喜欢
        • 2022-12-07
        • 2016-02-26
        • 1970-01-01
        • 2020-05-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-14
        • 2018-09-24
        相关资源
        最近更新 更多