|
const { createHash, randomBytes } = require('node:crypto'); |
|
const { mkdir, readFile, rm, writeFile } = require('node:fs/promises'); |
|
const path = require('node:path'); |
|
const { EntitySchema } = require('@mikro-orm/core'); |
|
const { Migrator } = require('@mikro-orm/migrations'); |
|
const { MikroORM } = require('@mikro-orm/postgresql'); |
|
const { Client } = require('pg'); |
|
|
|
const suffix = `${Date.now()}_${randomBytes(4).toString('hex')}`; |
|
const databaseName = `mikro_orm_external_trigger_${suffix}`; |
|
const outputDirectory = path.join(process.cwd(), `.repro-${suffix}`); |
|
const snapshotPath = path.join(outputDirectory, '.snapshot-repro.json'); |
|
const connection = { |
|
host: process.env.DB_HOST ?? '127.0.0.1', |
|
password: process.env.DB_PASSWORD ?? 'postgres', |
|
port: Number(process.env.DB_PORT ?? 5432), |
|
user: process.env.DB_USER ?? 'postgres', |
|
}; |
|
const Sample = new EntitySchema({ |
|
name: 'Sample', |
|
tableName: 'sample', |
|
properties: { |
|
id: { type: 'number', primary: true }, |
|
}, |
|
}); |
|
|
|
async function main() { |
|
await createDatabase(); |
|
await mkdir(outputDirectory); |
|
|
|
const orm = await MikroORM.init({ |
|
...connection, |
|
dbName: databaseName, |
|
entities: [Sample], |
|
extensions: [Migrator], |
|
migrations: { |
|
emit: 'cjs', |
|
path: outputDirectory, |
|
snapshot: true, |
|
snapshotName: '.snapshot-repro', |
|
}, |
|
}); |
|
|
|
try { |
|
const migration = await orm.migrator.create(); |
|
const migrationPath = path.join(outputDirectory, migration.fileName); |
|
const generatedCode = await readFile(migrationPath, 'utf8'); |
|
const migrationCode = generatedCode.replace( |
|
'\n }\n\n async down() {', |
|
` |
|
this.addSql(\` |
|
create function bump_sample_version() |
|
returns trigger |
|
language plpgsql |
|
as $function$ |
|
begin |
|
return null; |
|
end; |
|
$function$; |
|
\`); |
|
this.addSql(\` |
|
create trigger sample_external_trigger |
|
after insert or update or delete |
|
on sample |
|
for each statement |
|
execute function bump_sample_version(); |
|
\`); |
|
} |
|
|
|
async down() {` |
|
); |
|
|
|
await writeFile(migrationPath, migrationCode); |
|
|
|
const before = await inspectSnapshot(); |
|
|
|
await orm.migrator.up(); |
|
|
|
const after = await inspectSnapshot(); |
|
const migrationNeeded = await orm.migrator.checkSchema(); |
|
const result = { after, before, migrationNeeded }; |
|
|
|
console.log(JSON.stringify(result, undefined, 2)); |
|
|
|
if ( |
|
before.triggerCount !== 0 || |
|
after.triggerCount !== 1 || |
|
!migrationNeeded |
|
) { |
|
throw new Error('The expected regression was not reproduced'); |
|
} |
|
} finally { |
|
await orm.close(true); |
|
await dropDatabase(); |
|
await rm(outputDirectory, { force: true, recursive: true }); |
|
} |
|
} |
|
|
|
async function inspectSnapshot() { |
|
const content = await readFile(snapshotPath, 'utf8'); |
|
const snapshot = JSON.parse(content); |
|
const triggerCount = snapshot.tables.reduce( |
|
(count, table) => count + (table.triggers?.length ?? 0), |
|
0 |
|
); |
|
|
|
return { |
|
hash: createHash('sha256').update(content).digest('hex'), |
|
triggerCount, |
|
}; |
|
} |
|
|
|
async function createDatabase() { |
|
await runAdminQuery(`create database "${databaseName}"`); |
|
} |
|
|
|
async function dropDatabase() { |
|
await runAdminQuery(`drop database if exists "${databaseName}" with (force)`); |
|
} |
|
|
|
async function runAdminQuery(query) { |
|
const client = new Client({ ...connection, database: 'postgres' }); |
|
|
|
await client.connect(); |
|
|
|
try { |
|
await client.query(query); |
|
} finally { |
|
await client.end(); |
|
} |
|
} |
|
|
|
main().catch((error) => { |
|
console.error(error); |
|
process.exitCode = 1; |
|
}); |