Better migration locking behaviour
This commit is contained in:
		| @@ -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
									
								
							
							
						
						
									
										3
									
								
								sql/migrations/lock.sql
									
									
									
									
									
										Normal 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; | ||||
							
								
								
									
										1
									
								
								sql/migrations/unlock.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								sql/migrations/unlock.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| UPDATE migrations_lock SET is_locked = false WHERE is_locked = true; | ||||
| @@ -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[]> { | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -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": [] | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user