From 9de4e30cac0d07045b081ca8823e70ec3cc10f60 Mon Sep 17 00:00:00 2001 From: shogdahl Date: Thu, 30 Dec 2021 13:04:55 -0500 Subject: [PATCH] Split queries out of multi-command string --- README.md | 18 +++++++++ .../run-migration/fixtures/split-queries.sql | 8 ++++ src/__unit__/run-migration/index.ts | 40 +++++++++++++++++++ src/run-migration.ts | 16 +++++++- 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/__unit__/run-migration/fixtures/split-queries.sql diff --git a/README.md b/README.md index 5247883..340c259 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,24 @@ Running in a transaction ensures each migration is atomic. Either it completes s An exception is made when `-- postgres-migrations disable-transaction` is included at the top of the migration file. This allows migrations such as `CREATE INDEX CONCURRENTLY` which cannot be run inside a transaction. +### Each migration is run as a multi-command string + +The entire migration file is run as a single command string no matter how many queries there are. + +An exception is made when `-- postgres-migrations split-queries` is commented at the top of the migration file. This allows migrations to include multiple query string blocks to be run separately. Each block of queries that start with the comment `-- split-query` will be run as its own command string. + +Example: adding values to enums can't be run multi-command strings so rather than adding a single migration per value added the following migration will run each alter statement separately. +``` +-- postgres-migrations disable-transaction +-- postgres-migrations split-queries + +-- split-query +ALTER TYPE eggs ADD VALUE IF NOT EXISTS 'Fried'; + +-- split-query +ALTER TYPE eggs ADD VALUE IF NOT EXISTS 'Scrambled'; +``` + ### Abort on errors If anything fails, the migration in progress is rolled back and an exception is thrown. diff --git a/src/__unit__/run-migration/fixtures/split-queries.sql b/src/__unit__/run-migration/fixtures/split-queries.sql new file mode 100644 index 0000000..f57cd7a --- /dev/null +++ b/src/__unit__/run-migration/fixtures/split-queries.sql @@ -0,0 +1,8 @@ +-- postgres-migrations disable-transaction +-- postgres-migrations split-queries + +-- split-query +ALTER TYPE eggs ADD VALUE IF NOT EXISTS 'Fried'; + +-- split-query +ALTER TYPE eggs ADD VALUE IF NOT EXISTS 'Scrambled'; diff --git a/src/__unit__/run-migration/index.ts b/src/__unit__/run-migration/index.ts index 64c85d4..15de57f 100644 --- a/src/__unit__/run-migration/index.ts +++ b/src/__unit__/run-migration/index.ts @@ -12,6 +12,7 @@ const readFile = promisify(fsReadFile) let normalSqlFile: string let normalJsFile: string let noTransactionSqlFile: string +let splitQueriesSqlFile: string test.before(async () => { await Promise.all([ @@ -25,6 +26,12 @@ test.before(async () => { }, ), + readFile(__dirname + "/fixtures/split-queries.sql", "utf8").then( + (contents) => { + splitQueriesSqlFile = contents + }, + ), + Promise.resolve().then(() => { normalJsFile = loadSqlFromJs(__dirname + "/fixtures/normal.sql.js") }), @@ -152,3 +159,36 @@ test("does not roll back when there is an error inside a transactiony migration" ) }) }) + +test("runs each query separately when instructed", (t) => { + const query = sinon.stub().resolves() + const run = runMigration(migrationTableName, {query}) + + const migration = buildMigration(splitQueriesSqlFile) + const splitQueries = migration.sql.split("-- split-query") + + return run(migration).then(() => { + t.is(query.callCount, 4) + t.is( + query.firstCall.args[0], + splitQueries[0], + "could ignore the first entry (comments only)", + ) + t.is( + query.secondCall.args[0], + splitQueries[1], + "should run the first migration", + ) + t.is( + query.thirdCall.args[0], + splitQueries[2], + "should run the second migration", + ) + + t.deepEqual( + query.lastCall.args[0].values, + [migration.id, migration.name, migration.hash], + "should record the running of the migration in the database", + ) + }) +}) diff --git a/src/run-migration.ts b/src/run-migration.ts index 9a969b1..8488bb9 100644 --- a/src/run-migration.ts +++ b/src/run-migration.ts @@ -4,6 +4,13 @@ import {Logger, Migration, BasicPgClient} from "./types" const noop = () => { // } + +const split = async (migration: Migration, client: BasicPgClient) => { + const queries = migration.sql.split("-- split-query") + + await Promise.all(queries.map(async (query: string) => client.query(query))) +} + const insertMigration = async ( migrationTableName: string, client: BasicPgClient, @@ -30,6 +37,9 @@ export const runMigration = migration.sql.includes("-- postgres-migrations disable-transaction") === false + const splitQueries = + migration.sql.includes("-- postgres-migrations split-queries") === false + log(`Running migration in transaction: ${inTransaction}`) const begin = inTransaction ? () => client.query("START TRANSACTION") : noop @@ -38,9 +48,13 @@ export const runMigration = const cleanup = inTransaction ? () => client.query("ROLLBACK") : noop + const run = splitQueries + ? () => client.query(migration.sql) + : () => split(migration, client) + try { await begin() - await client.query(migration.sql) + await run() await insertMigration(migrationTableName, client, migration, log) await end()