ddlforge 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * ddlforge - Exponential backoff with full jitter for lock-timeout retry loops.
3
+ *
4
+ * Algorithm: sleep = Math.random() * Math.min(maxDelay, baseDelay * 2 ** attempt)
5
+ * This prevents lock-convoy / stampede effects by randomising each retry interval.
6
+ */
7
+ /**
8
+ * Computes a full-jitter exponential backoff interval for the given attempt index.
9
+ *
10
+ * @param attempt 0-based attempt index (first retry = 0)
11
+ * @param options Backoff configuration
12
+ * @returns Randomised sleep duration in milliseconds
13
+ */
14
+ export function computeBackoff(attempt, options = {}) {
15
+ const baseDelayMs = options.baseDelayMs ?? 250;
16
+ const maxDelayMs = options.maxDelayMs ?? 10_000;
17
+ const maxRetries = options.maxRetries ?? 5;
18
+ const ceiling = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, attempt));
19
+ const sleepMs = Math.random() * ceiling;
20
+ const exceeded = attempt >= maxRetries;
21
+ return { sleepMs, attempt, exceeded };
22
+ }
23
+ /**
24
+ * Returns a promise that resolves after `ms` milliseconds.
25
+ * Optionally accepts an AbortSignal: if the signal is aborted before the
26
+ * timer fires, the promise rejects with the signal's reason.
27
+ *
28
+ * @param ms Sleep duration in milliseconds
29
+ * @param signal Optional AbortSignal for cancellation
30
+ */
31
+ export function sleep(ms, signal) {
32
+ return new Promise((resolve, reject) => {
33
+ if (signal?.aborted) {
34
+ reject(signal.reason ?? new Error('AbortError'));
35
+ return;
36
+ }
37
+ const timer = setTimeout(() => {
38
+ signal?.removeEventListener('abort', onAbort);
39
+ resolve();
40
+ }, ms);
41
+ function onAbort() {
42
+ clearTimeout(timer);
43
+ reject(this.reason ?? new Error('AbortError'));
44
+ }
45
+ signal?.addEventListener('abort', onAbort, { once: true });
46
+ });
47
+ }
48
+ /**
49
+ * Iterates over retry attempts, yielding the computed backoff for each attempt.
50
+ * Callers should `sleep(backoff.sleepMs)` after receiving each non-first result.
51
+ *
52
+ * Example:
53
+ * ```ts
54
+ * for (const backoff of retryIterator(3, opts)) {
55
+ * if (backoff.attempt > 0) await sleep(backoff.sleepMs, signal);
56
+ * // ... attempt work ...
57
+ * }
58
+ * ```
59
+ */
60
+ export function* retryIterator(maxRetries, options = {}) {
61
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
62
+ yield computeBackoff(attempt, { ...options, maxRetries });
63
+ }
64
+ }
65
+ //# sourceMappingURL=backoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backoff.js","sourceRoot":"","sources":["../../../src/runner/backoff.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoBH;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,UAA0B,EAAE;IAC1E,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,GAAG,CAAC;IAC/C,MAAM,UAAU,GAAI,OAAO,CAAC,UAAU,IAAK,MAAM,CAAC;IAClD,MAAM,UAAU,GAAI,OAAO,CAAC,UAAU,IAAK,CAAC,CAAC;IAE7C,MAAM,OAAO,GAAI,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1E,MAAM,OAAO,GAAI,IAAI,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC;IACzC,MAAM,QAAQ,GAAG,OAAO,IAAI,UAAU,CAAC;IAEvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACxC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,MAAoB;IACpD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;YACjD,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,SAAS,OAAO;YACd,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAE,IAAoB,CAAC,MAAM,IAAI,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;QAClE,CAAC;QAED,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,SAAS,CAAC,CAAC,aAAa,CAC5B,UAAkB,EAClB,UAA8C,EAAE;IAEhD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,MAAM,cAAc,CAAC,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * ddlforge - PostgreSQL DDL execution supervisor.
3
+ *
4
+ * Responsibilities
5
+ * ────────────────
6
+ * 1. Dynamically imports `pg` at runtime (graceful error if not installed).
7
+ * 2. Parses SQL migration files into individual statements (reuses the existing
8
+ * zero-dependency lexer — no duplicate parser code).
9
+ * 3. For each statement, selects one of two execution paths:
10
+ *
11
+ * a. **Non-transactional path** (CREATE/DROP INDEX CONCURRENTLY, REINDEX
12
+ * CONCURRENTLY, VACUUM):
13
+ * – Sets session-level `lock_timeout` / `statement_timeout`.
14
+ * – Executes the statement outside any transaction block.
15
+ * – Always RESETs the session timeouts in a `finally` block.
16
+ *
17
+ * b. **Transactional path** (all other statements):
18
+ * – Opens a transaction with `BEGIN`.
19
+ * – Injects `SET LOCAL lock_timeout` and `SET LOCAL statement_timeout`.
20
+ * – Executes the statement.
21
+ * – On error 55P03 (lock_not_available) or 57014 (query_canceled):
22
+ * Rolls back, waits for a full-jitter backoff interval, retries.
23
+ * – On avalanche detected by the lock monitor: rolls back and aborts.
24
+ * – On success: commits and proceeds to the next statement.
25
+ *
26
+ * 4. Reports live progress via an optional EventEmitter-compatible callback.
27
+ *
28
+ * Zero-dependency invariant
29
+ * ─────────────────────────
30
+ * `pg` is imported with `await import('pg')` inside an async function so it
31
+ * never appears in the static import graph. Static linting (`ddlforge check`)
32
+ * works without `pg` installed.
33
+ */
34
+ export interface ExecutorOptions {
35
+ /** Full PostgreSQL connection string or URL */
36
+ databaseUrl: string;
37
+ /** lock_timeout value (e.g. "2500ms" or "2500"). Default: "3000ms" */
38
+ lockTimeout?: string;
39
+ /** statement_timeout value (e.g. "30000ms"). Default: "30000ms" */
40
+ statementTimeout?: string;
41
+ /** Maximum number of retry attempts per statement on lock errors. Default: 5 */
42
+ maxRetries?: number;
43
+ /** If true, parse and validate statements but never execute them. Default: false */
44
+ dryRun?: boolean;
45
+ /**
46
+ * Number of blocked backends that triggers an avalanche abort.
47
+ * Default: 1 (abort as soon as even one query queues behind ours).
48
+ */
49
+ lockQueueThreshold?: number;
50
+ /** Milliseconds between lock-monitor polls. Default: 500 */
51
+ monitorPollMs?: number;
52
+ /** Progress callback invoked after each statement attempt */
53
+ onProgress?: (event: ExecutionEvent) => void;
54
+ /** AbortSignal for external cancellation */
55
+ signal?: AbortSignal;
56
+ }
57
+ export type ExecutionEventKind = 'statement-start' | 'statement-success' | 'statement-retry' | 'statement-failed' | 'avalanche-abort' | 'dry-run-statement' | 'migration-complete' | 'migration-failed';
58
+ export interface ExecutionEvent {
59
+ kind: ExecutionEventKind;
60
+ statementIndex: number;
61
+ totalStatements: number;
62
+ statementSql: string;
63
+ attempt: number;
64
+ elapsedMs: number;
65
+ error?: string;
66
+ lockTimeoutMs?: number;
67
+ retryBackoffMs?: number;
68
+ }
69
+ export interface ExecutionResult {
70
+ success: boolean;
71
+ statementsExecuted: number;
72
+ statementsTotal: number;
73
+ durationMs: number;
74
+ error?: string;
75
+ avalanche?: boolean;
76
+ }
77
+ /**
78
+ * Returns true for statements that PostgreSQL forbids inside a transaction
79
+ * block and must therefore run outside BEGIN … COMMIT:
80
+ * - CREATE INDEX CONCURRENTLY
81
+ * - CREATE UNIQUE INDEX CONCURRENTLY
82
+ * - DROP INDEX CONCURRENTLY
83
+ * - REINDEX … CONCURRENTLY
84
+ * - VACUUM (any form)
85
+ *
86
+ * Detection is intentionally conservative: we normalise whitespace and
87
+ * uppercase the first ~120 characters so that leading comments or extra
88
+ * spaces never cause a false negative.
89
+ */
90
+ export declare function isConcurrentStatement(sql: string): boolean;
91
+ /**
92
+ * Executes a SQL migration file against a PostgreSQL database with:
93
+ * - per-statement lock-timeout injection
94
+ * - exponential full-jitter retry on lock acquisition failures
95
+ * - background lock-queue monitoring with automatic cancellation
96
+ *
97
+ * @param sql Full text of the migration file
98
+ * @param options Execution configuration
99
+ */
100
+ export declare function executeMigration(sql: string, options: ExecutorOptions): Promise<ExecutionResult>;
101
+ /**
102
+ * Parses a SQL migration string and returns the list of individual statements
103
+ * that would be executed. Useful for tooling and dry-run previews.
104
+ */
105
+ export declare function parseMigrationStatements(sql: string): string[];
106
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/runner/executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAUH,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,4CAA4C;IAC5C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,MAAM,kBAAkB,GAC1B,iBAAiB,GACjB,mBAAmB,GACnB,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,GACjB,mBAAmB,GACnB,oBAAoB,GACpB,kBAAkB,CAAC;AAEvB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAqFD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAS1D;AAoBD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,eAAe,CAAC,CA2U1B;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9D"}
@@ -0,0 +1,444 @@
1
+ /**
2
+ * ddlforge - PostgreSQL DDL execution supervisor.
3
+ *
4
+ * Responsibilities
5
+ * ────────────────
6
+ * 1. Dynamically imports `pg` at runtime (graceful error if not installed).
7
+ * 2. Parses SQL migration files into individual statements (reuses the existing
8
+ * zero-dependency lexer — no duplicate parser code).
9
+ * 3. For each statement, selects one of two execution paths:
10
+ *
11
+ * a. **Non-transactional path** (CREATE/DROP INDEX CONCURRENTLY, REINDEX
12
+ * CONCURRENTLY, VACUUM):
13
+ * – Sets session-level `lock_timeout` / `statement_timeout`.
14
+ * – Executes the statement outside any transaction block.
15
+ * – Always RESETs the session timeouts in a `finally` block.
16
+ *
17
+ * b. **Transactional path** (all other statements):
18
+ * – Opens a transaction with `BEGIN`.
19
+ * – Injects `SET LOCAL lock_timeout` and `SET LOCAL statement_timeout`.
20
+ * – Executes the statement.
21
+ * – On error 55P03 (lock_not_available) or 57014 (query_canceled):
22
+ * Rolls back, waits for a full-jitter backoff interval, retries.
23
+ * – On avalanche detected by the lock monitor: rolls back and aborts.
24
+ * – On success: commits and proceeds to the next statement.
25
+ *
26
+ * 4. Reports live progress via an optional EventEmitter-compatible callback.
27
+ *
28
+ * Zero-dependency invariant
29
+ * ─────────────────────────
30
+ * `pg` is imported with `await import('pg')` inside an async function so it
31
+ * never appears in the static import graph. Static linting (`ddlforge check`)
32
+ * works without `pg` installed.
33
+ */
34
+ import { splitStatements } from '../lexer/sqlTokenizer.js';
35
+ import { sleep, computeBackoff } from './backoff.js';
36
+ import { startLockMonitor, isLockError } from './locksMonitor.js';
37
+ /**
38
+ * Dynamically imports pg and returns the Pool and Client constructors.
39
+ * If pg is not installed, prints clear installation instructions and throws.
40
+ */
41
+ async function loadPg() {
42
+ try {
43
+ // Dynamic import: NOT in static import graph — ddlforge check still works
44
+ const mod = await import('pg');
45
+ return mod.default;
46
+ }
47
+ catch {
48
+ const msg = [
49
+ '',
50
+ ' ╔══════════════════════════════════════════════════════════╗',
51
+ ' ║ ddlforge apply requires the "pg" package to be ║',
52
+ ' ║ installed in your project. ║',
53
+ ' ║ ║',
54
+ ' ║ Run one of: ║',
55
+ ' ║ npm install pg ║',
56
+ ' ║ yarn add pg ║',
57
+ ' ║ pnpm add pg ║',
58
+ ' ╚══════════════════════════════════════════════════════════╝',
59
+ '',
60
+ ].join('\n');
61
+ process.stderr.write(msg + '\n');
62
+ throw new Error('pg package is not installed. Run: npm install pg');
63
+ }
64
+ }
65
+ /* ------------------------------------------------------------------ */
66
+ /* Helpers */
67
+ /* ------------------------------------------------------------------ */
68
+ /**
69
+ * Normalises a timeout value into a bare integer millisecond string
70
+ * suitable for `SET LOCAL lock_timeout = '<value>'`.
71
+ *
72
+ * Accepts:
73
+ * - "3000ms" → "3000"
74
+ * - "3s" → "3000" (NOT handled — pass pre-converted ms values)
75
+ * - "3000" → "3000"
76
+ * - 3000 → "3000"
77
+ */
78
+ function normaliseTimeoutMs(value) {
79
+ const s = String(value).trim();
80
+ if (s.endsWith('ms')) {
81
+ return s.slice(0, -2);
82
+ }
83
+ return s;
84
+ }
85
+ /**
86
+ * Returns the `code` from a Postgres-driver error object.
87
+ * pg errors expose `.code` as a top-level property.
88
+ */
89
+ function pgErrorCode(err) {
90
+ if (err !== null && typeof err === 'object' && 'code' in err) {
91
+ return String(err['code'] ?? '');
92
+ }
93
+ return '';
94
+ }
95
+ /**
96
+ * Returns true for statements that PostgreSQL forbids inside a transaction
97
+ * block and must therefore run outside BEGIN … COMMIT:
98
+ * - CREATE INDEX CONCURRENTLY
99
+ * - CREATE UNIQUE INDEX CONCURRENTLY
100
+ * - DROP INDEX CONCURRENTLY
101
+ * - REINDEX … CONCURRENTLY
102
+ * - VACUUM (any form)
103
+ *
104
+ * Detection is intentionally conservative: we normalise whitespace and
105
+ * uppercase the first ~120 characters so that leading comments or extra
106
+ * spaces never cause a false negative.
107
+ */
108
+ export function isConcurrentStatement(sql) {
109
+ // Strip leading whitespace / line comments and take the first ~120 chars.
110
+ const head = sql.trimStart().slice(0, 120).replace(/\s+/g, ' ').toUpperCase();
111
+ if (/^CREATE(\s+UNIQUE)?\s+INDEX\s+CONCURRENTLY\b/.test(head))
112
+ return true;
113
+ if (/^DROP\s+INDEX\s+CONCURRENTLY\b/.test(head))
114
+ return true;
115
+ if (/^REINDEX\b/.test(head) && head.includes('CONCURRENTLY'))
116
+ return true;
117
+ if (/^VACUUM\b/.test(head))
118
+ return true;
119
+ return false;
120
+ }
121
+ /* ------------------------------------------------------------------ */
122
+ /* Statement text reconstruction */
123
+ /* ------------------------------------------------------------------ */
124
+ /**
125
+ * Extracts the original SQL text for a statement from the migration file.
126
+ * We reconstruct it from the token raw values so that whitespace is
127
+ * preserved reasonably well for display purposes.
128
+ */
129
+ function statementText(raw) {
130
+ // The lexer joins tokens with single spaces; we just use the raw field.
131
+ return raw.trimEnd() + ';';
132
+ }
133
+ /* ------------------------------------------------------------------ */
134
+ /* Main executor */
135
+ /* ------------------------------------------------------------------ */
136
+ /**
137
+ * Executes a SQL migration file against a PostgreSQL database with:
138
+ * - per-statement lock-timeout injection
139
+ * - exponential full-jitter retry on lock acquisition failures
140
+ * - background lock-queue monitoring with automatic cancellation
141
+ *
142
+ * @param sql Full text of the migration file
143
+ * @param options Execution configuration
144
+ */
145
+ export async function executeMigration(sql, options) {
146
+ const { databaseUrl, lockTimeout = '3000ms', statementTimeout = '30000ms', maxRetries = 5, dryRun = false, lockQueueThreshold = 1, monitorPollMs = 500, onProgress, signal, } = options;
147
+ const lockTimeoutMs = normaliseTimeoutMs(lockTimeout);
148
+ const statementTimeoutMs = normaliseTimeoutMs(statementTimeout);
149
+ const migrationStart = Date.now();
150
+ // ── Parse statements (zero-dependency lexer) ────────────────────────
151
+ const statements = splitStatements(sql);
152
+ const total = statements.length;
153
+ if (total === 0) {
154
+ return { success: true, statementsExecuted: 0, statementsTotal: 0, durationMs: 0 };
155
+ }
156
+ // ── Dry run mode ─────────────────────────────────────────────────────
157
+ if (dryRun) {
158
+ for (let i = 0; i < statements.length; i++) {
159
+ const stmt = statements[i];
160
+ onProgress?.({
161
+ kind: 'dry-run-statement',
162
+ statementIndex: i,
163
+ totalStatements: total,
164
+ statementSql: statementText(stmt.raw),
165
+ attempt: 0,
166
+ elapsedMs: Date.now() - migrationStart,
167
+ });
168
+ }
169
+ return {
170
+ success: true,
171
+ statementsExecuted: 0,
172
+ statementsTotal: total,
173
+ durationMs: Date.now() - migrationStart,
174
+ };
175
+ }
176
+ // ── Load pg ──────────────────────────────────────────────────────────
177
+ const { Pool, Client } = await loadPg();
178
+ // One pool for the migration; monitor gets its own direct clients
179
+ const pool = new Pool({ connectionString: databaseUrl, max: 1 });
180
+ let statementsExecuted = 0;
181
+ let globalError;
182
+ let avalanche = false;
183
+ try {
184
+ for (let i = 0; i < statements.length; i++) {
185
+ if (signal?.aborted) {
186
+ globalError = 'Execution aborted by caller signal.';
187
+ break;
188
+ }
189
+ const stmt = statements[i];
190
+ const stmtSql = statementText(stmt.raw);
191
+ const stmtStart = Date.now();
192
+ let succeeded = false;
193
+ let stmtError;
194
+ onProgress?.({
195
+ kind: 'statement-start',
196
+ statementIndex: i,
197
+ totalStatements: total,
198
+ statementSql: stmtSql,
199
+ attempt: 0,
200
+ elapsedMs: stmtStart - migrationStart,
201
+ lockTimeoutMs: Number(lockTimeoutMs),
202
+ });
203
+ // ── Retry loop per statement ──────────────────────────────────────
204
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
205
+ if (signal?.aborted)
206
+ break;
207
+ // Jitter-sleep before all retries (not before first attempt)
208
+ if (attempt > 0) {
209
+ const backoff = computeBackoff(attempt - 1, {
210
+ baseDelayMs: 250,
211
+ maxDelayMs: 10_000,
212
+ maxRetries,
213
+ });
214
+ onProgress?.({
215
+ kind: 'statement-retry',
216
+ statementIndex: i,
217
+ totalStatements: total,
218
+ statementSql: stmtSql,
219
+ attempt,
220
+ elapsedMs: Date.now() - migrationStart,
221
+ retryBackoffMs: backoff.sleepMs,
222
+ });
223
+ await sleep(backoff.sleepMs, signal);
224
+ if (signal?.aborted)
225
+ break;
226
+ }
227
+ // Grab a client from the pool for this attempt
228
+ const client = await pool.connect();
229
+ // Snapshot our backend PID for the lock monitor
230
+ let migrationPid = -1;
231
+ try {
232
+ const pidResult = await client.query('SELECT pg_backend_pid() AS pid');
233
+ migrationPid = Number(pidResult.rows[0]?.['pid'] ?? -1);
234
+ }
235
+ catch {
236
+ // Fallback: proceed without monitoring
237
+ }
238
+ // Start the lock monitor on its own independent connection
239
+ const monitorClientFactory = async () => {
240
+ const monClient = new Client({ connectionString: databaseUrl });
241
+ await monClient.connect?.();
242
+ return monClient;
243
+ };
244
+ const monitor = migrationPid > 0
245
+ ? startLockMonitor({
246
+ migrationPid,
247
+ clientFactory: monitorClientFactory,
248
+ pollIntervalMs: monitorPollMs,
249
+ queueThreshold: lockQueueThreshold,
250
+ signal,
251
+ })
252
+ : null;
253
+ const isConcurrent = isConcurrentStatement(stmtSql);
254
+ if (isConcurrent) {
255
+ // ── Non-transactional path (CONCURRENTLY / VACUUM) ─────────────
256
+ // PostgreSQL forbids these inside a transaction block.
257
+ // Apply session-level timeouts instead of transaction-local ones
258
+ // and always RESET them in the finally block.
259
+ try {
260
+ await client.query(`SET lock_timeout = '${lockTimeoutMs}'`);
261
+ await client.query(`SET statement_timeout = '${statementTimeoutMs}'`);
262
+ // Execute the statement outside any transaction
263
+ await client.query(stmtSql);
264
+ // Stop monitor — execution succeeded, no avalanche
265
+ monitor?.stop();
266
+ const monResult = await monitor?.result;
267
+ if (monResult?.avalanche) {
268
+ // Race: monitor fired just as the statement completed.
269
+ // Statement already succeeded so treat as success.
270
+ }
271
+ succeeded = true;
272
+ stmtError = undefined;
273
+ break; // exit retry loop
274
+ }
275
+ catch (err) {
276
+ const code = pgErrorCode(err);
277
+ // Stop monitor before doing anything else
278
+ monitor?.stop();
279
+ const monResult = await monitor?.result;
280
+ // No transaction to roll back for concurrent statements.
281
+ if (monResult?.avalanche) {
282
+ // Lock-queue avalanche: abort the entire migration
283
+ onProgress?.({
284
+ kind: 'avalanche-abort',
285
+ statementIndex: i,
286
+ totalStatements: total,
287
+ statementSql: stmtSql,
288
+ attempt,
289
+ elapsedMs: Date.now() - migrationStart,
290
+ error: `Lock-queue avalanche: ${monResult.blockedCount} backend(s) queued. Cancelled at ${monResult.cancelledAt}.`,
291
+ });
292
+ globalError = `Lock-queue avalanche detected. ${monResult.blockedCount} backend(s) were waiting behind migration PID ${migrationPid}. Migration aborted to protect connection pool.`;
293
+ avalanche = true;
294
+ break;
295
+ }
296
+ if (isLockError(code) && attempt < maxRetries) {
297
+ // Retryable lock error — continue retry loop
298
+ stmtError = `[${code}] ${err.message ?? String(err)}`;
299
+ continue;
300
+ }
301
+ // Non-retryable error or max retries exhausted
302
+ stmtError = `[${code || 'ERR'}] ${err.message ?? String(err)}`;
303
+ break;
304
+ }
305
+ finally {
306
+ // Always reset session-level timeouts so the connection is
307
+ // returned to the pool in a clean state.
308
+ try {
309
+ await client.query('RESET lock_timeout');
310
+ }
311
+ catch { /* ignore */ }
312
+ try {
313
+ await client.query('RESET statement_timeout');
314
+ }
315
+ catch { /* ignore */ }
316
+ client.release?.();
317
+ }
318
+ }
319
+ else {
320
+ // ── Transactional path (all other DDL / DML) ────────────────────
321
+ try {
322
+ // Open transaction
323
+ await client.query('BEGIN');
324
+ // Inject session-local timeouts (scoped to this transaction)
325
+ await client.query(`SET LOCAL lock_timeout = '${lockTimeoutMs}'`);
326
+ await client.query(`SET LOCAL statement_timeout = '${statementTimeoutMs}'`);
327
+ // Execute the DDL statement
328
+ await client.query(stmtSql);
329
+ // Commit
330
+ await client.query('COMMIT');
331
+ // Stop monitor — execution succeeded, no avalanche
332
+ monitor?.stop();
333
+ const monResult = await monitor?.result;
334
+ if (monResult?.avalanche) {
335
+ // Race condition: monitor fired just as we committed.
336
+ // The COMMIT succeeded so we treat this as a success.
337
+ // (pg_cancel_backend after COMMIT is a no-op.)
338
+ }
339
+ succeeded = true;
340
+ stmtError = undefined;
341
+ break; // exit retry loop
342
+ }
343
+ catch (err) {
344
+ const code = pgErrorCode(err);
345
+ // Stop monitor before doing anything else
346
+ monitor?.stop();
347
+ const monResult = await monitor?.result;
348
+ // Rollback the failed transaction
349
+ try {
350
+ await client.query('ROLLBACK');
351
+ }
352
+ catch { /* ignore */ }
353
+ if (monResult?.avalanche) {
354
+ // Lock-queue avalanche: abort the entire migration
355
+ onProgress?.({
356
+ kind: 'avalanche-abort',
357
+ statementIndex: i,
358
+ totalStatements: total,
359
+ statementSql: stmtSql,
360
+ attempt,
361
+ elapsedMs: Date.now() - migrationStart,
362
+ error: `Lock-queue avalanche: ${monResult.blockedCount} backend(s) queued. Cancelled at ${monResult.cancelledAt}.`,
363
+ });
364
+ globalError = `Lock-queue avalanche detected. ${monResult.blockedCount} backend(s) were waiting behind migration PID ${migrationPid}. Migration aborted to protect connection pool.`;
365
+ avalanche = true;
366
+ break;
367
+ }
368
+ if (isLockError(code) && attempt < maxRetries) {
369
+ // Retryable lock error — continue retry loop
370
+ stmtError = `[${code}] ${err.message ?? String(err)}`;
371
+ continue;
372
+ }
373
+ // Non-retryable error or max retries exhausted
374
+ stmtError = `[${code || 'ERR'}] ${err.message ?? String(err)}`;
375
+ break;
376
+ }
377
+ finally {
378
+ client.release?.();
379
+ }
380
+ }
381
+ } // end retry loop
382
+ if (avalanche)
383
+ break;
384
+ if (signal?.aborted && !succeeded) {
385
+ globalError = 'Execution aborted by caller signal.';
386
+ break;
387
+ }
388
+ if (succeeded) {
389
+ statementsExecuted++;
390
+ onProgress?.({
391
+ kind: 'statement-success',
392
+ statementIndex: i,
393
+ totalStatements: total,
394
+ statementSql: stmtSql,
395
+ attempt: 0,
396
+ elapsedMs: Date.now() - stmtStart,
397
+ });
398
+ }
399
+ else {
400
+ globalError = stmtError ?? 'Unknown execution error.';
401
+ onProgress?.({
402
+ kind: 'statement-failed',
403
+ statementIndex: i,
404
+ totalStatements: total,
405
+ statementSql: stmtSql,
406
+ attempt: maxRetries,
407
+ elapsedMs: Date.now() - stmtStart,
408
+ error: globalError,
409
+ });
410
+ break;
411
+ }
412
+ }
413
+ }
414
+ finally {
415
+ await pool.end().catch(() => { });
416
+ }
417
+ const durationMs = Date.now() - migrationStart;
418
+ const success = !globalError;
419
+ onProgress?.({
420
+ kind: success ? 'migration-complete' : 'migration-failed',
421
+ statementIndex: statementsExecuted,
422
+ totalStatements: total,
423
+ statementSql: '',
424
+ attempt: 0,
425
+ elapsedMs: durationMs,
426
+ error: globalError,
427
+ });
428
+ return {
429
+ success,
430
+ statementsExecuted,
431
+ statementsTotal: total,
432
+ durationMs,
433
+ error: globalError,
434
+ avalanche: avalanche || undefined,
435
+ };
436
+ }
437
+ /**
438
+ * Parses a SQL migration string and returns the list of individual statements
439
+ * that would be executed. Useful for tooling and dry-run previews.
440
+ */
441
+ export function parseMigrationStatements(sql) {
442
+ return splitStatements(sql).map(s => statementText(s.raw));
443
+ }
444
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/runner/executor.ts"],"names":[],"mappings":"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"}