pipework 0.17.1 → 0.19.0
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.
- package/CHANGELOG.md +31 -0
- package/dist/REFERENCE.md +1190 -0
- package/dist/cli/commands/migrate.d.ts +16 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +35 -4
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +0 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/data/migrate/apply.d.ts +5 -7
- package/dist/data/migrate/apply.d.ts.map +1 -1
- package/dist/data/migrate/apply.js +7 -95
- package/dist/data/migrate/apply.js.map +1 -1
- package/dist/data/migrate/check.d.ts +11 -4
- package/dist/data/migrate/check.d.ts.map +1 -1
- package/dist/data/migrate/check.js +40 -22
- package/dist/data/migrate/check.js.map +1 -1
- package/dist/data/migrate/index.d.ts +2 -1
- package/dist/data/migrate/index.d.ts.map +1 -1
- package/dist/data/migrate/index.js +1 -0
- package/dist/data/migrate/index.js.map +1 -1
- package/dist/data/migrate/post-process.d.ts.map +1 -1
- package/dist/data/migrate/post-process.js +15 -5
- package/dist/data/migrate/post-process.js.map +1 -1
- package/dist/data/migrate/pragma.d.ts +35 -0
- package/dist/data/migrate/pragma.d.ts.map +1 -0
- package/dist/data/migrate/pragma.js +47 -0
- package/dist/data/migrate/pragma.js.map +1 -0
- package/dist/data/migrate/run.d.ts +1 -3
- package/dist/data/migrate/run.d.ts.map +1 -1
- package/dist/data/migrate/run.js +9 -9
- package/dist/data/migrate/run.js.map +1 -1
- package/dist/data/migrate/safety.d.ts +1 -1
- package/dist/data/migrate/safety.d.ts.map +1 -1
- package/dist/data/migrate/safety.js +41 -20
- package/dist/data/migrate/safety.js.map +1 -1
- package/dist/data/migrate/statements.d.ts +65 -0
- package/dist/data/migrate/statements.d.ts.map +1 -0
- package/dist/data/migrate/statements.js +290 -0
- package/dist/data/migrate/statements.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration pragmas — per-file declarations read from the migration's own
|
|
3
|
+
* comments.
|
|
4
|
+
*
|
|
5
|
+
* Authorization for a hazardous migration lives here rather than on the
|
|
6
|
+
* `pipework migrate` command line, and the distinction is the whole point.
|
|
7
|
+
* A CLI flag authorizes an *invocation*: it is written once, into a deploy
|
|
8
|
+
* workflow, by someone who cannot know what migration 0071 will contain nine
|
|
9
|
+
* months later. The first destructive migration that has to ship puts
|
|
10
|
+
* `--allow-destructive` in CI permanently, and from then on the gate is
|
|
11
|
+
* decorative — every later migration is pre-approved by a decision nobody
|
|
12
|
+
* revisits, and nothing about it shows up in review.
|
|
13
|
+
*
|
|
14
|
+
* A pragma authorizes a *file*. It is written by the person writing the
|
|
15
|
+
* destructive statement, at the moment they write it; it appears in the diff,
|
|
16
|
+
* so it is reviewed; it is versioned, so `git log` says who approved which
|
|
17
|
+
* drop; and it cannot go blanket — a pragma in 0065 says nothing about 0071.
|
|
18
|
+
* The deploy command stays a bare `pipework migrate` forever, which means
|
|
19
|
+
* there is no flag to erode.
|
|
20
|
+
*
|
|
21
|
+
* The migration tracker already records a content hash per applied migration
|
|
22
|
+
* and refuses edited-but-applied files, so a pragma cannot be quietly added
|
|
23
|
+
* after the fact.
|
|
24
|
+
*/
|
|
25
|
+
export const PRAGMA_PREFIX = '-- pipework:';
|
|
26
|
+
export const PRAGMA_FOR = {
|
|
27
|
+
destructive: 'pipework:expect-destructive',
|
|
28
|
+
locking: 'pipework:expect-locking',
|
|
29
|
+
dynamic: 'pipework:expect-dynamic',
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* True when `sql` declares the given pragma. Pragmas are recognized only on
|
|
33
|
+
* their own comment line, so prose that happens to mention one does not
|
|
34
|
+
* authorize anything.
|
|
35
|
+
*/
|
|
36
|
+
export function hasPragma(sql, kind) {
|
|
37
|
+
const wanted = PRAGMA_FOR[kind];
|
|
38
|
+
for (const line of sql.split('\n')) {
|
|
39
|
+
const trimmed = line.trim();
|
|
40
|
+
if (!trimmed.startsWith('--'))
|
|
41
|
+
continue;
|
|
42
|
+
if (trimmed.slice(2).trim() === wanted)
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=pragma.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pragma.js","sourceRoot":"","sources":["../../../src/data/migrate/pragma.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAA;AAK3C,MAAM,CAAC,MAAM,UAAU,GAA+B;IACpD,WAAW,EAAE,6BAA6B;IAC1C,OAAO,EAAE,yBAAyB;IAClC,OAAO,EAAE,yBAAyB;CACnC,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,IAAgB;IACrD,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IAC/B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QAC3B,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAQ;QACvC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,MAAM;YAAE,OAAO,IAAI,CAAA;IACrD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
|
|
@@ -2,8 +2,6 @@ import type { Manifold } from '../../core/pipework.js';
|
|
|
2
2
|
import type { ResolvedDatabase } from '../../core/config/index.js';
|
|
3
3
|
import { type MigrationCheckResult } from './check.js';
|
|
4
4
|
export interface MigrateOptions {
|
|
5
|
-
allowDestructive?: boolean;
|
|
6
|
-
acknowledgeLock?: boolean;
|
|
7
5
|
}
|
|
8
6
|
export interface MigrateResult {
|
|
9
7
|
database: string;
|
|
@@ -17,5 +15,5 @@ export declare class UnsafeMigrationError extends Error {
|
|
|
17
15
|
constructor(check: MigrationCheckResult, message: string);
|
|
18
16
|
}
|
|
19
17
|
export declare function migrateAll(instance: Manifold, options?: MigrateOptions): Promise<MigrateResult[]>;
|
|
20
|
-
export declare function migrateOne(instance: Manifold, dbConfig: ResolvedDatabase,
|
|
18
|
+
export declare function migrateOne(instance: Manifold, dbConfig: ResolvedDatabase, _options?: MigrateOptions): Promise<MigrateResult>;
|
|
21
19
|
//# sourceMappingURL=run.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/data/migrate/run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/data/migrate/run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAQvE,MAAM,WAAW,cAAc;CAAG;AAElC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,WAAW,EAAE,oBAAoB,CAAA;CAClC;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aAG3B,KAAK,EAAE,oBAAoB;IAF7C,SAAkB,IAAI,0BAAyB;gBAE7B,KAAK,EAAE,oBAAoB,EAC3C,OAAO,EAAE,MAAM;CAIlB;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAU3G;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CA4CtI"}
|
package/dist/data/migrate/run.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function migrateAll(instance, options = {}) {
|
|
|
21
21
|
}
|
|
22
22
|
return results;
|
|
23
23
|
}
|
|
24
|
-
export async function migrateOne(instance, dbConfig,
|
|
24
|
+
export async function migrateOne(instance, dbConfig, _options = {}) {
|
|
25
25
|
const conn = instance.pool.getOrCreate(dbConfig);
|
|
26
26
|
// Scope the safety gate to pending migrations only — hazards in already-applied
|
|
27
27
|
// migrations are history, not a decision the operator still gets to make.
|
|
@@ -29,14 +29,14 @@ export async function migrateOne(instance, dbConfig, options = {}) {
|
|
|
29
29
|
// history can never run a clean no-op `migrate`.
|
|
30
30
|
const safetyCheck = dbConfig.migrations !== undefined
|
|
31
31
|
? checkMigrations(dbConfig.migrations, await readAppliedState(conn.drizzle, dbConfig.migrationTable))
|
|
32
|
-
: { safe: true, hasDestructive: false, hasLocking: false, fileHazards: [] };
|
|
33
|
-
if (safetyCheck.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
throw new UnsafeMigrationError(safetyCheck, `Migration for "${dbConfig.name}"
|
|
39
|
-
`
|
|
32
|
+
: { safe: true, hasDestructive: false, hasLocking: false, hasDynamic: false, declared: true, fileHazards: [] };
|
|
33
|
+
if (!safetyCheck.declared) {
|
|
34
|
+
const files = safetyCheck.fileHazards.filter(f => f.undeclared.length > 0);
|
|
35
|
+
const detail = files
|
|
36
|
+
.map(f => `${f.file} (${f.undeclared.join(', ')})`)
|
|
37
|
+
.join(', ');
|
|
38
|
+
throw new UnsafeMigrationError(safetyCheck, `Migration for "${dbConfig.name}" raises hazards that its own file does not declare: ${detail}. ` +
|
|
39
|
+
`Add the matching \`-- pipework:expect-*\` pragma to each migration and commit it.`);
|
|
40
40
|
}
|
|
41
41
|
let extensionsCreated = [];
|
|
42
42
|
if (dbConfig.extensions.length > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/data/migrate/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAGvC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,eAAe,EAA6B,MAAM,YAAY,CAAA;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/data/migrate/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../../src/data/migrate/safety.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../../src/data/migrate/safety.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAA;AAErE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB;AAuGD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAwBzD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB9E"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { scanStatements, blankLiterals } from './statements.js';
|
|
1
2
|
const RULES = [
|
|
2
3
|
// ── Destructive (data loss / backward-incompatible) ───────────────────
|
|
3
4
|
{
|
|
@@ -63,15 +64,41 @@ const RULES = [
|
|
|
63
64
|
operation: 'ADD FOREIGN KEY (without NOT VALID)',
|
|
64
65
|
suggestion: 'Add the constraint with NOT VALID, then VALIDATE CONSTRAINT in a separate statement.',
|
|
65
66
|
},
|
|
67
|
+
{
|
|
68
|
+
// Locking rather than destructive: on a non-empty table this fails loudly
|
|
69
|
+
// instead of losing data, so it aborts the deploy rather than silently
|
|
70
|
+
// damaging it. It is still a gate-worthy hazard, hence the rule (#334).
|
|
71
|
+
pattern: /\bADD\s+COLUMN\b[^;]*\bNOT\s+NULL\b/i,
|
|
72
|
+
severity: 'locking',
|
|
73
|
+
operation: 'ADD COLUMN NOT NULL (without DEFAULT)',
|
|
74
|
+
suggestion: 'Add the column nullable, backfill, then SET NOT NULL — or give it a DEFAULT so existing rows can be filled.',
|
|
75
|
+
except: /\bDEFAULT\b/i,
|
|
76
|
+
},
|
|
77
|
+
// ── Dynamic (SQL assembled at runtime — unanalyzable) ────────────────
|
|
78
|
+
{
|
|
79
|
+
// The gate reads text; `EXECUTE` runs text that does not exist yet. Rather
|
|
80
|
+
// than pretend to see through it, name it. This is also what lets string
|
|
81
|
+
// literals be treated as the inert data they almost always are: a literal
|
|
82
|
+
// is dangerous exactly when something EXECUTEs it, and that is structural
|
|
83
|
+
// rather than a guess about its contents (#334).
|
|
84
|
+
pattern: /\bEXECUTE\b/i,
|
|
85
|
+
severity: 'dynamic',
|
|
86
|
+
operation: 'dynamic SQL (EXECUTE)',
|
|
87
|
+
suggestion: 'The gate cannot analyze SQL built at runtime. Prefer static DDL; if runtime assembly is required, declare it in the migration.',
|
|
88
|
+
},
|
|
66
89
|
];
|
|
67
90
|
export function analyzeSQL(sql) {
|
|
68
|
-
const statements =
|
|
91
|
+
const statements = scanStatements(sql);
|
|
69
92
|
const hazards = [];
|
|
70
93
|
for (const stmt of statements) {
|
|
94
|
+
// Rules match against the statement with string-literal contents blanked:
|
|
95
|
+
// literals are data, and DDL keywords sitting in seed data execute
|
|
96
|
+
// nothing. Dollar-quoted bodies stay intact — those are code.
|
|
97
|
+
const subject = blankLiterals(stmt);
|
|
71
98
|
for (const rule of RULES) {
|
|
72
|
-
if (!rule.pattern.test(
|
|
99
|
+
if (!rule.pattern.test(subject))
|
|
73
100
|
continue;
|
|
74
|
-
if (rule.except !== undefined && rule.except.test(
|
|
101
|
+
if (rule.except !== undefined && rule.except.test(subject))
|
|
75
102
|
continue;
|
|
76
103
|
hazards.push({
|
|
77
104
|
severity: rule.severity,
|
|
@@ -83,26 +110,20 @@ export function analyzeSQL(sql) {
|
|
|
83
110
|
}
|
|
84
111
|
return hazards;
|
|
85
112
|
}
|
|
86
|
-
function splitStatements(sql) {
|
|
87
|
-
return sql
|
|
88
|
-
.split(/;/)
|
|
89
|
-
.map(s => s.trim())
|
|
90
|
-
.filter(s => s.length > 0 && !s.startsWith('--'));
|
|
91
|
-
}
|
|
92
113
|
export function formatHazards(hazards, file) {
|
|
93
|
-
const destructive = hazards.filter(h => h.severity === 'destructive');
|
|
94
|
-
const locking = hazards.filter(h => h.severity === 'locking');
|
|
95
114
|
const lines = [];
|
|
96
115
|
lines.push(` ${file}:`);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
for (const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
116
|
+
const label = {
|
|
117
|
+
destructive: '✗ DESTRUCTIVE',
|
|
118
|
+
locking: '⚠ LOCKING',
|
|
119
|
+
dynamic: '? DYNAMIC',
|
|
120
|
+
};
|
|
121
|
+
for (const severity of ['destructive', 'locking', 'dynamic']) {
|
|
122
|
+
for (const h of hazards.filter(x => x.severity === severity)) {
|
|
123
|
+
lines.push(` ${label[severity]}: ${h.operation}`);
|
|
124
|
+
lines.push(` ${truncate(h.statement, 120)}`);
|
|
125
|
+
lines.push(` → ${h.suggestion}`);
|
|
126
|
+
}
|
|
106
127
|
}
|
|
107
128
|
return lines.join('\n');
|
|
108
129
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../../../src/data/migrate/safety.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../../../src/data/migrate/safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One SQL scanner, shared by everything in the migrate pipeline that has to
|
|
3
|
+
* reason about statement boundaries.
|
|
4
|
+
*
|
|
5
|
+
* There were two hand-rolled splitters before this, and they disagreed. The
|
|
6
|
+
* runner (`parseStatements`) learned string literals and dollar-quoted bodies
|
|
7
|
+
* in #317 so a plpgsql function body would survive; the safety gate kept a
|
|
8
|
+
* `sql.split(';')` that then discarded any fragment starting with `--`. Since
|
|
9
|
+
* a statement and the comment above it land in the *same* fragment, the gate
|
|
10
|
+
* dropped comment-led statements entirely and reported migrations clean that
|
|
11
|
+
* dropped tables (#334). Neither splitter knew about block comments or quoted
|
|
12
|
+
* identifiers.
|
|
13
|
+
*
|
|
14
|
+
* The rules, in one place:
|
|
15
|
+
*
|
|
16
|
+
* - `--` runs to end of line; block comments nest, per Postgres.
|
|
17
|
+
* - Comments are removed, replaced by whitespace so they still separate tokens.
|
|
18
|
+
* - `'…'`, `"…"` and `$tag$…$tag$` are copied verbatim — a `;` or `--` inside
|
|
19
|
+
* them is content, not syntax. Backslash escapes apply only in `E'…'`.
|
|
20
|
+
* - Comment state and quote state are decided in the same pass. That ordering
|
|
21
|
+
* is load-bearing: an apostrophe in a comment (`-- the migration's ancestors`)
|
|
22
|
+
* must not open a string literal, and a `--` inside a literal must not open
|
|
23
|
+
* a comment. Either check running first, alone, gets the other case wrong.
|
|
24
|
+
* - An unterminated comment or literal runs to end of input; the SQL is invalid
|
|
25
|
+
* either way and Postgres is what should say so.
|
|
26
|
+
*/
|
|
27
|
+
export interface ScanOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Honor drizzle's `--> statement-breakpoint` as an explicit statement
|
|
30
|
+
* separator. The runner needs this (it decides what is sent to Postgres as
|
|
31
|
+
* one statement); the safety gate does not care.
|
|
32
|
+
*/
|
|
33
|
+
breakpointSeparator?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Splits SQL into statements, stripping comments. */
|
|
36
|
+
export declare function scanStatements(sql: string, options?: ScanOptions): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Removes comments, leaving literals and dollar-quoted bodies intact and
|
|
39
|
+
* statement separators where they were. For callers that need to test SQL
|
|
40
|
+
* against a pattern without a comment supplying a false match or hiding a
|
|
41
|
+
* real one.
|
|
42
|
+
*/
|
|
43
|
+
export declare function stripComments(sql: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Replaces the *contents* of `'…'` string literals with spaces, keeping the
|
|
46
|
+
* quotes and the overall length.
|
|
47
|
+
*
|
|
48
|
+
* A string literal in a migration is data — `INSERT INTO notes VALUES
|
|
49
|
+
* ('a; DROP TABLE x')` drops nothing. Matching hazard rules against literal
|
|
50
|
+
* text raises a hazard for inert seed data, and no amount of pattern-tuning
|
|
51
|
+
* fixes that, because "is this text dangerous" is not answerable from the
|
|
52
|
+
* text. What makes a literal dangerous is `EXECUTE` running it, which is a
|
|
53
|
+
* structural fact and gets its own rule.
|
|
54
|
+
*
|
|
55
|
+
* Dollar-quoted bodies are deliberately NOT blanked: `DO $$ BEGIN DROP TABLE
|
|
56
|
+
* x; END $$` is code, and it executes during the migration.
|
|
57
|
+
*/
|
|
58
|
+
export declare function blankLiterals(sql: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Index of the first character of actual SQL in `sql` — i.e. past any leading
|
|
61
|
+
* whitespace and comments. Lets a text rewriter skip a leading comment block
|
|
62
|
+
* without rewriting words inside it.
|
|
63
|
+
*/
|
|
64
|
+
export declare function codeStartOffset(sql: string): number;
|
|
65
|
+
//# sourceMappingURL=statements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../../../src/data/migrate/statements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAMH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,EAAE,CAqE/E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmCjD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmDjD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAoBnD"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One SQL scanner, shared by everything in the migrate pipeline that has to
|
|
3
|
+
* reason about statement boundaries.
|
|
4
|
+
*
|
|
5
|
+
* There were two hand-rolled splitters before this, and they disagreed. The
|
|
6
|
+
* runner (`parseStatements`) learned string literals and dollar-quoted bodies
|
|
7
|
+
* in #317 so a plpgsql function body would survive; the safety gate kept a
|
|
8
|
+
* `sql.split(';')` that then discarded any fragment starting with `--`. Since
|
|
9
|
+
* a statement and the comment above it land in the *same* fragment, the gate
|
|
10
|
+
* dropped comment-led statements entirely and reported migrations clean that
|
|
11
|
+
* dropped tables (#334). Neither splitter knew about block comments or quoted
|
|
12
|
+
* identifiers.
|
|
13
|
+
*
|
|
14
|
+
* The rules, in one place:
|
|
15
|
+
*
|
|
16
|
+
* - `--` runs to end of line; block comments nest, per Postgres.
|
|
17
|
+
* - Comments are removed, replaced by whitespace so they still separate tokens.
|
|
18
|
+
* - `'…'`, `"…"` and `$tag$…$tag$` are copied verbatim — a `;` or `--` inside
|
|
19
|
+
* them is content, not syntax. Backslash escapes apply only in `E'…'`.
|
|
20
|
+
* - Comment state and quote state are decided in the same pass. That ordering
|
|
21
|
+
* is load-bearing: an apostrophe in a comment (`-- the migration's ancestors`)
|
|
22
|
+
* must not open a string literal, and a `--` inside a literal must not open
|
|
23
|
+
* a comment. Either check running first, alone, gets the other case wrong.
|
|
24
|
+
* - An unterminated comment or literal runs to end of input; the SQL is invalid
|
|
25
|
+
* either way and Postgres is what should say so.
|
|
26
|
+
*/
|
|
27
|
+
const IDENT_CHAR = /[A-Za-z0-9_]/;
|
|
28
|
+
const STATEMENT_BREAKPOINT = '--> statement-breakpoint';
|
|
29
|
+
/** Splits SQL into statements, stripping comments. */
|
|
30
|
+
export function scanStatements(sql, options = {}) {
|
|
31
|
+
const statements = [];
|
|
32
|
+
let buf = '';
|
|
33
|
+
const flush = () => {
|
|
34
|
+
const stmt = buf.trim();
|
|
35
|
+
if (stmt.length > 0)
|
|
36
|
+
statements.push(stmt);
|
|
37
|
+
buf = '';
|
|
38
|
+
};
|
|
39
|
+
let i = 0;
|
|
40
|
+
while (i < sql.length) {
|
|
41
|
+
const c = sql[i];
|
|
42
|
+
const next = sql[i + 1];
|
|
43
|
+
// Line comment — runs to the newline, which is left to land as whitespace.
|
|
44
|
+
if (c === '-' && next === '-') {
|
|
45
|
+
const nl = sql.indexOf('\n', i);
|
|
46
|
+
const end = nl === -1 ? sql.length : nl;
|
|
47
|
+
if (options.breakpointSeparator === true && sql.slice(i, end).trim() === STATEMENT_BREAKPOINT)
|
|
48
|
+
flush();
|
|
49
|
+
i = end;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// Block comment — Postgres nests these, so track depth.
|
|
53
|
+
if (c === '/' && next === '*') {
|
|
54
|
+
i = skipBlockComment(sql, i);
|
|
55
|
+
buf += ' ';
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// Dollar-quoted body — nothing inside is special, only the matching tag ends it.
|
|
59
|
+
const tag = dollarTagAt(sql, i);
|
|
60
|
+
if (tag !== null) {
|
|
61
|
+
const close = sql.indexOf(tag, i + tag.length);
|
|
62
|
+
const end = close === -1 ? sql.length : close + tag.length;
|
|
63
|
+
buf += sql.slice(i, end);
|
|
64
|
+
i = end;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// String literal. Backslashes escape only in an E'' string.
|
|
68
|
+
if (c === "'") {
|
|
69
|
+
const end = consumeQuoted(sql, i, "'", isEscapeString(sql, i));
|
|
70
|
+
buf += sql.slice(i, end);
|
|
71
|
+
i = end;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// Quoted identifier.
|
|
75
|
+
if (c === '"') {
|
|
76
|
+
const end = consumeQuoted(sql, i, '"', false);
|
|
77
|
+
buf += sql.slice(i, end);
|
|
78
|
+
i = end;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (c === ';') {
|
|
82
|
+
flush();
|
|
83
|
+
i++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
buf += c;
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
flush();
|
|
90
|
+
return statements;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Removes comments, leaving literals and dollar-quoted bodies intact and
|
|
94
|
+
* statement separators where they were. For callers that need to test SQL
|
|
95
|
+
* against a pattern without a comment supplying a false match or hiding a
|
|
96
|
+
* real one.
|
|
97
|
+
*/
|
|
98
|
+
export function stripComments(sql) {
|
|
99
|
+
let out = '';
|
|
100
|
+
let i = 0;
|
|
101
|
+
while (i < sql.length) {
|
|
102
|
+
const c = sql[i];
|
|
103
|
+
const next = sql[i + 1];
|
|
104
|
+
if (c === '-' && next === '-') {
|
|
105
|
+
const nl = sql.indexOf('\n', i);
|
|
106
|
+
i = nl === -1 ? sql.length : nl;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (c === '/' && next === '*') {
|
|
110
|
+
i = skipBlockComment(sql, i);
|
|
111
|
+
out += ' ';
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const tag = dollarTagAt(sql, i);
|
|
115
|
+
if (tag !== null) {
|
|
116
|
+
const close = sql.indexOf(tag, i + tag.length);
|
|
117
|
+
const end = close === -1 ? sql.length : close + tag.length;
|
|
118
|
+
out += sql.slice(i, end);
|
|
119
|
+
i = end;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (c === "'" || c === '"') {
|
|
123
|
+
const end = consumeQuoted(sql, i, c, c === "'" && isEscapeString(sql, i));
|
|
124
|
+
out += sql.slice(i, end);
|
|
125
|
+
i = end;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
out += c;
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Replaces the *contents* of `'…'` string literals with spaces, keeping the
|
|
135
|
+
* quotes and the overall length.
|
|
136
|
+
*
|
|
137
|
+
* A string literal in a migration is data — `INSERT INTO notes VALUES
|
|
138
|
+
* ('a; DROP TABLE x')` drops nothing. Matching hazard rules against literal
|
|
139
|
+
* text raises a hazard for inert seed data, and no amount of pattern-tuning
|
|
140
|
+
* fixes that, because "is this text dangerous" is not answerable from the
|
|
141
|
+
* text. What makes a literal dangerous is `EXECUTE` running it, which is a
|
|
142
|
+
* structural fact and gets its own rule.
|
|
143
|
+
*
|
|
144
|
+
* Dollar-quoted bodies are deliberately NOT blanked: `DO $$ BEGIN DROP TABLE
|
|
145
|
+
* x; END $$` is code, and it executes during the migration.
|
|
146
|
+
*/
|
|
147
|
+
export function blankLiterals(sql) {
|
|
148
|
+
let out = '';
|
|
149
|
+
let i = 0;
|
|
150
|
+
while (i < sql.length) {
|
|
151
|
+
const c = sql[i];
|
|
152
|
+
const next = sql[i + 1];
|
|
153
|
+
if (c === '-' && next === '-') {
|
|
154
|
+
const nl = sql.indexOf('\n', i);
|
|
155
|
+
const end = nl === -1 ? sql.length : nl;
|
|
156
|
+
out += sql.slice(i, end);
|
|
157
|
+
i = end;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (c === '/' && next === '*') {
|
|
161
|
+
const end = skipBlockComment(sql, i);
|
|
162
|
+
out += sql.slice(i, end);
|
|
163
|
+
i = end;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const tag = dollarTagAt(sql, i);
|
|
167
|
+
if (tag !== null) {
|
|
168
|
+
// A dollar-quoted body is code, so it is kept — but the literals inside
|
|
169
|
+
// that code are still data, so recurse rather than copying it whole.
|
|
170
|
+
// `DO $$ BEGIN DROP TABLE x; END $$` must keep its DROP TABLE, while
|
|
171
|
+
// `DO $$ BEGIN EXECUTE 'DROP TABLE x'; END $$` must lose it and be
|
|
172
|
+
// caught by the EXECUTE rule instead.
|
|
173
|
+
const close = sql.indexOf(tag, i + tag.length);
|
|
174
|
+
const end = close === -1 ? sql.length : close + tag.length;
|
|
175
|
+
const inner = sql.slice(i + tag.length, close === -1 ? sql.length : close);
|
|
176
|
+
out += tag + blankLiterals(inner) + (close === -1 ? '' : tag);
|
|
177
|
+
i = end;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (c === "'") {
|
|
181
|
+
const end = consumeQuoted(sql, i, "'", isEscapeString(sql, i));
|
|
182
|
+
const body = sql.slice(i + 1, Math.max(i + 1, end - 1));
|
|
183
|
+
out += "'" + body.replace(/[^\n]/g, ' ') + (end - i >= 2 ? "'" : '');
|
|
184
|
+
i = end;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (c === '"') {
|
|
188
|
+
const end = consumeQuoted(sql, i, '"', false);
|
|
189
|
+
out += sql.slice(i, end);
|
|
190
|
+
i = end;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
out += c;
|
|
194
|
+
i++;
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Index of the first character of actual SQL in `sql` — i.e. past any leading
|
|
200
|
+
* whitespace and comments. Lets a text rewriter skip a leading comment block
|
|
201
|
+
* without rewriting words inside it.
|
|
202
|
+
*/
|
|
203
|
+
export function codeStartOffset(sql) {
|
|
204
|
+
let i = 0;
|
|
205
|
+
while (i < sql.length) {
|
|
206
|
+
const c = sql[i];
|
|
207
|
+
if (/\s/.test(c)) {
|
|
208
|
+
i++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (c === '-' && sql[i + 1] === '-') {
|
|
212
|
+
const nl = sql.indexOf('\n', i);
|
|
213
|
+
i = nl === -1 ? sql.length : nl;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (c === '/' && sql[i + 1] === '*') {
|
|
217
|
+
i = skipBlockComment(sql, i);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
return i;
|
|
221
|
+
}
|
|
222
|
+
return sql.length;
|
|
223
|
+
}
|
|
224
|
+
/** `start` indexes the opening `/*`. Returns the index just past the matching close. */
|
|
225
|
+
function skipBlockComment(sql, start) {
|
|
226
|
+
let depth = 0;
|
|
227
|
+
let i = start;
|
|
228
|
+
while (i < sql.length) {
|
|
229
|
+
if (sql[i] === '/' && sql[i + 1] === '*') {
|
|
230
|
+
depth++;
|
|
231
|
+
i += 2;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (sql[i] === '*' && sql[i + 1] === '/') {
|
|
235
|
+
depth--;
|
|
236
|
+
i += 2;
|
|
237
|
+
if (depth === 0)
|
|
238
|
+
return i;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
i++;
|
|
242
|
+
}
|
|
243
|
+
return sql.length;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Returns the dollar-quote tag opening at `i` (`$$`, `$body$`), or null.
|
|
247
|
+
* Tags may not start with a digit, which is what keeps `$1` positional
|
|
248
|
+
* parameters from being read as the start of a quoted body.
|
|
249
|
+
*/
|
|
250
|
+
function dollarTagAt(sql, i) {
|
|
251
|
+
if (sql[i] !== '$')
|
|
252
|
+
return null;
|
|
253
|
+
let j = i + 1;
|
|
254
|
+
while (j < sql.length && IDENT_CHAR.test(sql[j]))
|
|
255
|
+
j++;
|
|
256
|
+
if (sql[j] !== '$')
|
|
257
|
+
return null;
|
|
258
|
+
if (/[0-9]/.test(sql[i + 1] ?? ''))
|
|
259
|
+
return null;
|
|
260
|
+
return sql.slice(i, j + 1);
|
|
261
|
+
}
|
|
262
|
+
/** True when the quote at `i` opens an `E''` escape string. */
|
|
263
|
+
function isEscapeString(sql, i) {
|
|
264
|
+
const prev = sql[i - 1];
|
|
265
|
+
if (prev !== 'e' && prev !== 'E')
|
|
266
|
+
return false;
|
|
267
|
+
const before = sql[i - 2];
|
|
268
|
+
return before === undefined || !IDENT_CHAR.test(before);
|
|
269
|
+
}
|
|
270
|
+
/** Returns the index just past the closing quote, or end of input if unterminated. */
|
|
271
|
+
function consumeQuoted(sql, start, quote, backslashEscapes) {
|
|
272
|
+
let i = start + 1;
|
|
273
|
+
while (i < sql.length) {
|
|
274
|
+
const c = sql[i];
|
|
275
|
+
if (backslashEscapes && c === '\\') {
|
|
276
|
+
i += 2;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (c === quote) {
|
|
280
|
+
if (sql[i + 1] === quote) {
|
|
281
|
+
i += 2;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
return i + 1;
|
|
285
|
+
}
|
|
286
|
+
i++;
|
|
287
|
+
}
|
|
288
|
+
return sql.length;
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=statements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statements.js","sourceRoot":"","sources":["../../../src/data/migrate/statements.ts"],"names":[],"mappings":"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"}
|