tududi/backend/utils/migration-utils.js

176 lines
5.8 KiB
JavaScript

'use strict';
async function safeAddColumns(queryInterface, tableName, columns) {
try {
const tableInfo = await queryInterface.describeTable(tableName);
for (const column of columns) {
if (!(column.name in tableInfo)) {
await queryInterface.addColumn(
tableName,
column.name,
column.definition
);
}
}
} catch (error) {
console.log(`Migration error for table ${tableName}:`, error.message);
throw error;
}
}
async function safeCreateTable(queryInterface, tableName, tableDefinition) {
try {
const tableExists = await queryInterface
.showAllTables()
.then((tables) => tables.includes(tableName));
if (!tableExists) {
await queryInterface.createTable(tableName, tableDefinition);
}
} catch (error) {
console.log(
`Migration error creating table ${tableName}:`,
error.message
);
throw error;
}
}
async function safeAddIndex(queryInterface, tableName, fields, options = {}) {
try {
const indexes = await queryInterface.showIndex(tableName);
const indexExists = indexes.some((index) =>
index.fields.some((field) => fields.includes(field.attribute))
);
if (!indexExists) {
await queryInterface.addIndex(tableName, fields, options);
}
} catch (error) {
console.log(
`Migration error adding index to ${tableName}:`,
error.message
);
}
}
async function safeRemoveColumn(queryInterface, tableName, columnName) {
try {
const tableInfo = await queryInterface.describeTable(tableName);
if (!(columnName in tableInfo)) {
console.log(
`Column ${columnName} does not exist in ${tableName}, skipping removal`
);
return;
}
const dialect = queryInterface.sequelize.getDialect();
// SQLite doesn't support DROP COLUMN, so we need to recreate the table
if (dialect === 'sqlite') {
try {
// Get all columns except the one to remove
const columns = Object.keys(tableInfo).filter(
(col) => col !== columnName
);
// Build column definitions for new table
const columnDefs = columns
.map((col) => {
const info = tableInfo[col];
let def = `${col} ${info.type}`;
if (info.primaryKey) {
def += ' PRIMARY KEY';
}
if (info.autoIncrement) {
def += ' AUTOINCREMENT';
}
if (!info.allowNull) {
def += ' NOT NULL';
}
if (info.unique) {
def += ' UNIQUE';
}
if (
info.defaultValue !== undefined &&
info.defaultValue !== null
) {
// Properly quote string defaults
const defaultVal =
typeof info.defaultValue === 'string'
? `'${info.defaultValue.replace(/'/g, "''")}'`
: info.defaultValue;
def += ` DEFAULT ${defaultVal}`;
}
return def;
})
.join(', ');
const columnList = columns.join(', ');
// Execute operations separately as SQLite doesn't support multiple statements
await queryInterface.sequelize.query(
'PRAGMA foreign_keys = OFF;'
);
await queryInterface.sequelize.query(
`CREATE TABLE ${tableName}_new (${columnDefs});`
);
await queryInterface.sequelize.query(
`INSERT INTO ${tableName}_new (${columnList}) SELECT ${columnList} FROM ${tableName};`
);
await queryInterface.sequelize.query(
`DROP TABLE ${tableName};`
);
await queryInterface.sequelize.query(
`ALTER TABLE ${tableName}_new RENAME TO ${tableName};`
);
await queryInterface.sequelize.query(
'PRAGMA foreign_keys = ON;'
);
console.log(
`Successfully removed column ${columnName} from ${tableName}`
);
} catch (error) {
// Ensure foreign keys are re-enabled even on error
try {
await queryInterface.sequelize.query(
'PRAGMA foreign_keys = ON;'
);
} catch (pragmaError) {
// Ignore pragma errors during cleanup
}
console.log(
`Migration error removing column ${columnName} from ${tableName}:`,
error.message
);
throw error;
}
} else {
// For other databases, use standard removeColumn
await queryInterface.removeColumn(tableName, columnName);
}
} catch (error) {
console.log(
`Migration error removing column ${columnName} from ${tableName}:`,
error.message
);
throw error;
}
}
module.exports = {
safeAddColumns,
safeCreateTable,
safeAddIndex,
safeRemoveColumn,
};