Skip to content

Instantly share code, notes, and snippets.

@rbideau
Created February 16, 2023 10:25
Show Gist options
  • Save rbideau/711e77240e30c178da76e8953b6fd15f to your computer and use it in GitHub Desktop.
Save rbideau/711e77240e30c178da76e8953b6fd15f to your computer and use it in GitHub Desktop.
Fix enum statements in TypeORM migration
#!/usr/bin/env node
// This fix TypeORM enum creation, which generate multiple CREATE TYPE statement
// when an enum is used in multiple columns.
// The solution is taken from this SO answer: https://stackoverflow.com/a/48382296
// which gracefully handle the exception
// - https://github.com/typeorm/typeorm/issues/5648
// - https://github.com/typeorm/typeorm/issues/5738
// - https://github.com/typeorm/typeorm/issues/7501
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { argv, cwd, exit } from 'node:process';
/**
* Extract arguments from `argv`, validate filePath exists
*
* @returns {object} Contains `filePath`
*/
function extractArguments() {
// Validate argv
if (argv.length !== 3) {
console.log('Usage: yarn run db:migration:fix-enum <RELATIVE_FILE_PATH>');
exit(1);
}
// Extract arguments
const [, , filePath] = argv;
// Validate filePath exists
const absoluteFilePath = resolve(cwd(), filePath);
if (!existsSync(absoluteFilePath)) {
console.log(`file at ${absoluteFilePath} doesn't exists`);
exit(1);
}
console.log('arguments:');
console.log(`- filePath=${filePath}`);
return { absoluteFilePath };
}
/**
* Wrap all CREATE TYPE statement in `content` with DO/EXCEPTION
* to ignore exception when the type already exists
*
* @see https://stackoverflow.com/a/48382296
* @param {String} content
* @returns {String}
*/
function fixEnumCreateStatement(content) {
const regex =
/await queryRunner.query\(`\s+(CREATE TYPE ["',a-zA-Z_\s\(\.]+\))\s+`\);/g;
console.log(`Fix CREATE statement:`);
const replaceCreateTypeStatement = (match, createStatement) => {
console.log(` ${createStatement.trim().replaceAll(/\s+/g, ' ')}`);
return `await queryRunner.query(\`
DO $$ BEGIN
${createStatement};
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
\`);`;
};
const fixed = content.replaceAll(regex, replaceCreateTypeStatement);
return fixed;
}
/**
* Wrap all ALTER TYPE statement in `content` with DO/EXCEPTION
* to ignore exception when the type name already exists
*
* @param {String} content
* @returns {String}
*/
function fixEnumAlterStatement(content) {
const regex =
/await queryRunner.query\(`\s+(ALTER TYPE ["a-zA-Z_\s\.]+)\s+`\);/g;
console.log(`Fix ALTER statement:`);
const replaceCreateTypeStatement = (match, alterStatement) => {
console.log(` ${alterStatement.trim().replaceAll(/\s+/g, ' ')}`);
return `await queryRunner.query(\`
DO $$ BEGIN
${alterStatement};
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
\`);`;
};
const fixed = content.replaceAll(regex, replaceCreateTypeStatement);
return fixed;
}
/**
* Wrap all DROP TYPE statement in `content` with DO/EXCEPTION
* to ignore exception when the type is used in other table
*
* @param {String} content
* @returns {String}
*/
function fixEnumDropStatement(content) {
const regex =
/await queryRunner.query\(`\s+(DROP TYPE ["a-zA-Z_\s\.]+)\s+`\);/g;
console.log(`Fix DROP statement:`);
const replaceCreateTypeStatement = (match, dropStatement) => {
console.log(` ${dropStatement.trim()}`);
return `await queryRunner.query(\`
DO $$ BEGIN
${dropStatement};
EXCEPTION
WHEN dependent_objects_still_exist THEN null;
END $$;
\`);`;
};
const fixed = content.replaceAll(regex, replaceCreateTypeStatement);
return fixed;
}
// RUN
const { absoluteFilePath } = extractArguments();
let content = readFileSync(absoluteFilePath, { encoding: 'utf-8' });
content = fixEnumCreateStatement(content);
content = fixEnumAlterStatement(content);
content = fixEnumDropStatement(content);
writeFileSync(absoluteFilePath, content);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment