Better migration locking behaviour

This commit is contained in:
Gender Shrapnel 2019-10-22 13:41:21 +02:00
parent 5aced1b8b1
commit 6ab4c1e4d3
6 changed files with 32 additions and 2 deletions

View File

@ -2,4 +2,12 @@ CREATE TABLE IF NOT EXISTS migrations (
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name text UNIQUE NOT NULL,
applied_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
)
);
CREATE TABLE IF NOT EXISTS migrations_lock (
id integer PRIMARY KEY,
is_locked boolean NOT NULL DEFAULT false
);
INSERT INTO migrations_lock (id, is_locked) VALUES (1, false)
ON CONFLICT DO NOTHING;

3
sql/migrations/lock.sql Normal file
View File

@ -0,0 +1,3 @@
WITH rows as (
UPDATE migrations_lock SET is_locked = true WHERE is_locked = false RETURNING 1
) SELECT COUNT(*) FROM rows;

View File

@ -0,0 +1 @@
UPDATE migrations_lock SET is_locked = false WHERE is_locked = true;

View File

@ -19,6 +19,8 @@ import logger from "@kredens/logger";
import { DateTime } from "luxon";
import { IDatabase, IMain } from "pg-promise";
export class LockError extends Error {}
export class MigrationRepository {
private db: IDatabase<any>;
@ -30,7 +32,19 @@ export class MigrationRepository {
await this.db.none(sql.create);
}
public async lock() {
const count = await this.db.one(sql.lock);
if (+count.count !== 1) {
throw new LockError("Failed to acquire migration lock");
}
}
public async unlock() {
return this.db.none(sql.unlock);
}
public async apply() {
await this.lock();
const applied = (await this.applied()).map(m => m.name);
const toApply = sql.patches.filter(
p => p.up.isSome() && !applied.find(o => o === p.name)
@ -45,6 +59,7 @@ export class MigrationRepository {
})
.orLazy(() => Promise.resolve());
}
await this.unlock();
}
public async applied(): Promise<Migration[]> {

View File

@ -24,6 +24,8 @@ const migrations = {
applied: sql("migrations/applied.sql"),
apply: sql("migrations/apply.sql"),
create: sql("migrations/create.sql"),
lock: sql("migrations/lock.sql"),
unlock: sql("migrations/unlock.sql"),
patches: subdirs(path.join("migrations", "patches")).map(patchName => ({
down: ifExists(
path.join("migrations", "patches", patchName, "down.sql")

View File

@ -12,7 +12,8 @@
"@kredens"
],
"no-implicit-dependencies": [true, ["@kredens"]],
"object-literal-sort-keys": [true, "match-declaration-order-only", "shorthand-first"]
"object-literal-sort-keys": [true, "match-declaration-order-only", "shorthand-first"],
"max-classes-per-file": false
},
"rulesDirectory": []
}