web-dev-qa-db-fra.com

Déposer et créer ENUM avec séquencement correctement?

Comment supprimer correctement puis recréer le type ENUM avec séquencement pour Postgres dans les migrations? Par exemple, cette migration ne baisse pas enum_Users_status enum ... donc toutes les tentatives de recréation/modification des valeurs status après leur création ont échoué.

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

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

Finalement, j'ai réussi à supprimer le type d'énumération dans down, mais la migration de up (qui est censée créer cette énumération status à partir de zéro) échoue, disant quelque chose comme public.enum_Users_status le type d'énumération n'existe pas ..

10
ZenDD

MISE À JOUR: J'ai utilisé cela dans trois projets jusqu'à présent, j'ai donc décidé de créer un module npm: https://www.npmjs.com/package/replace-enum-postgresql .

J'ai fait un utilitaire pour le faire, j'espère que cela vous sera utile.

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 }));
  });
}

et voici mon exemple de migration :

'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

Si vous souhaitez changer/modifier le type d'énumération sans perdre de données. voici mon code de migration. j'espère que ça aide.

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

La suppression manuelle d'ENUM dans down a plutôt bien fonctionné pour moi.

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

Elaboration sur le post de shakir ullah et un commentaire sur github , voici ce qui a fonctionné pour moi:

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