@metaobjectsdev/migrate-ts 0.20.7 → 0.20.8
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/dist/emit/d1-cascade.d.ts +38 -0
- package/dist/emit/d1-cascade.d.ts.map +1 -0
- package/dist/emit/d1-cascade.js +98 -0
- package/dist/emit/d1-cascade.js.map +1 -0
- package/dist/emit/d1-fk-refuse.d.ts +10 -0
- package/dist/emit/d1-fk-refuse.d.ts.map +1 -1
- package/dist/emit/d1-fk-refuse.js +25 -0
- package/dist/emit/d1-fk-refuse.js.map +1 -1
- package/dist/emit/d1.d.ts +3 -1
- package/dist/emit/d1.d.ts.map +1 -1
- package/dist/emit/d1.js +54 -15
- package/dist/emit/d1.js.map +1 -1
- package/dist/emit/fk-graph.d.ts +28 -0
- package/dist/emit/fk-graph.d.ts.map +1 -0
- package/dist/emit/fk-graph.js +117 -0
- package/dist/emit/fk-graph.js.map +1 -0
- package/dist/emit/index.d.ts +2 -0
- package/dist/emit/index.d.ts.map +1 -1
- package/dist/emit/index.js +1 -1
- package/dist/emit/index.js.map +1 -1
- package/dist/emit/sqlite.d.ts +13 -1
- package/dist/emit/sqlite.d.ts.map +1 -1
- package/dist/emit/sqlite.js +15 -16
- package/dist/emit/sqlite.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/emit/d1-cascade.ts +134 -0
- package/src/emit/d1-fk-refuse.ts +27 -0
- package/src/emit/d1.ts +66 -15
- package/src/emit/fk-graph.ts +125 -0
- package/src/emit/index.ts +3 -1
- package/src/emit/sqlite.ts +21 -24
- package/src/index.ts +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Change, SchemaSnapshot } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Result of the D1 FK-cascade emitter: either a validated cascade (with the
|
|
4
|
+
* affected-table set so the dispatcher can partition the remaining changes) or a
|
|
5
|
+
* refusal because the affected tables form a foreign-key cycle.
|
|
6
|
+
*/
|
|
7
|
+
export type D1CascadeResult = {
|
|
8
|
+
up: string;
|
|
9
|
+
downWarning: string;
|
|
10
|
+
affected: Set<string>;
|
|
11
|
+
} | {
|
|
12
|
+
refuseCycle: string[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Emit the D1-legal FK-cascade rebuild for a set of recreated tables and every
|
|
16
|
+
* table transitively referencing them.
|
|
17
|
+
*
|
|
18
|
+
* On remote D1 the plain SQLite recreate-and-copy recipe fails: its `PRAGMA
|
|
19
|
+
* foreign_keys = OFF` is a no-op inside D1's implicit transaction, so dropping a
|
|
20
|
+
* referenced table raises "FOREIGN KEY constraint failed". This emitter instead
|
|
21
|
+
* rebuilds the WHOLE affected set inside one implicit transaction, deferring FK
|
|
22
|
+
* enforcement to commit via `PRAGMA defer_foreign_keys = ON`:
|
|
23
|
+
*
|
|
24
|
+
* 1. `PRAGMA defer_foreign_keys = ON;`
|
|
25
|
+
* 2. CREATE `__f_<t>` for each affected `t` (FKs whose target is also affected
|
|
26
|
+
* are rewritten to the target's temp name).
|
|
27
|
+
* 3. INSERT the carried columns from each old table into its temp.
|
|
28
|
+
* 4. DROP the old tables referrers-first (reverse topological order).
|
|
29
|
+
* 5. RENAME each temp to the real name parents-first (topological order),
|
|
30
|
+
* recreating that table's indexes immediately after its rename.
|
|
31
|
+
*
|
|
32
|
+
* No `BEGIN/COMMIT` and no `foreign_keys = OFF/ON` bracket: D1 runs the file in
|
|
33
|
+
* one implicit transaction and the safety pass would strip the former anyway.
|
|
34
|
+
*
|
|
35
|
+
* `renderD1` only calls this when `actualSchema` is present, so it is required.
|
|
36
|
+
*/
|
|
37
|
+
export declare function emitD1Cascade(changes: readonly Change[], expectedSchema: SchemaSnapshot, actualSchema: SchemaSnapshot, recreatedTables: ReadonlySet<string>): D1CascadeResult;
|
|
38
|
+
//# sourceMappingURL=d1-cascade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1-cascade.d.ts","sourceRoot":"","sources":["../../src/emit/d1-cascade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAmB,MAAM,aAAa,CAAC;AAiB3E;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAC1D;IAAE,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,EAAE,cAAc,EAC9B,YAAY,EAAE,cAAc,EAC5B,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,GACnC,eAAe,CA+EjB"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { renderCreateTable, renderCreateIndex, computeCarryColumns, changeTable, quote, } from "./sqlite.js";
|
|
2
|
+
import { buildFkEdges, unionEdges, affectedSet, topoOrder } from "./fk-graph.js";
|
|
3
|
+
/**
|
|
4
|
+
* Prefix for the transient rebuild tables the cascade recipe creates
|
|
5
|
+
* (`__f_<table>`). Distinct from SQLite's own `__new_<table>` recreate temp so
|
|
6
|
+
* the two recipes never collide.
|
|
7
|
+
*/
|
|
8
|
+
const TEMP_TABLE_PREFIX = "__f_";
|
|
9
|
+
/**
|
|
10
|
+
* Emit the D1-legal FK-cascade rebuild for a set of recreated tables and every
|
|
11
|
+
* table transitively referencing them.
|
|
12
|
+
*
|
|
13
|
+
* On remote D1 the plain SQLite recreate-and-copy recipe fails: its `PRAGMA
|
|
14
|
+
* foreign_keys = OFF` is a no-op inside D1's implicit transaction, so dropping a
|
|
15
|
+
* referenced table raises "FOREIGN KEY constraint failed". This emitter instead
|
|
16
|
+
* rebuilds the WHOLE affected set inside one implicit transaction, deferring FK
|
|
17
|
+
* enforcement to commit via `PRAGMA defer_foreign_keys = ON`:
|
|
18
|
+
*
|
|
19
|
+
* 1. `PRAGMA defer_foreign_keys = ON;`
|
|
20
|
+
* 2. CREATE `__f_<t>` for each affected `t` (FKs whose target is also affected
|
|
21
|
+
* are rewritten to the target's temp name).
|
|
22
|
+
* 3. INSERT the carried columns from each old table into its temp.
|
|
23
|
+
* 4. DROP the old tables referrers-first (reverse topological order).
|
|
24
|
+
* 5. RENAME each temp to the real name parents-first (topological order),
|
|
25
|
+
* recreating that table's indexes immediately after its rename.
|
|
26
|
+
*
|
|
27
|
+
* No `BEGIN/COMMIT` and no `foreign_keys = OFF/ON` bracket: D1 runs the file in
|
|
28
|
+
* one implicit transaction and the safety pass would strip the former anyway.
|
|
29
|
+
*
|
|
30
|
+
* `renderD1` only calls this when `actualSchema` is present, so it is required.
|
|
31
|
+
*/
|
|
32
|
+
export function emitD1Cascade(changes, expectedSchema, actualSchema, recreatedTables) {
|
|
33
|
+
const edges = unionEdges(buildFkEdges(expectedSchema), buildFkEdges(actualSchema));
|
|
34
|
+
const affectedAll = affectedSet(recreatedTables, edges);
|
|
35
|
+
// Restrict to tables that EXIST in the actual DB. `affectedSet` walks the
|
|
36
|
+
// expected edges too, so a brand-new table (created this migration) with an FK
|
|
37
|
+
// into a rebuilt table is pulled in as a referrer — but it cannot be
|
|
38
|
+
// INSERT...SELECTed or DROPped. New tables flow through the native "rest" path,
|
|
39
|
+
// emitted after the cascade so the renamed parent already exists.
|
|
40
|
+
const actualNames = new Set(actualSchema.tables.map((t) => t.name));
|
|
41
|
+
const affected = new Set([...affectedAll].filter((t) => actualNames.has(t)));
|
|
42
|
+
const { order, cycle } = topoOrder(affected, edges);
|
|
43
|
+
if (cycle)
|
|
44
|
+
return { refuseCycle: cycle };
|
|
45
|
+
const expectedByName = new Map(expectedSchema.tables.map((t) => [t.name, t]));
|
|
46
|
+
const expectedTable = (name) => {
|
|
47
|
+
const d = expectedByName.get(name);
|
|
48
|
+
if (!d)
|
|
49
|
+
throw new Error(`expectedSchema missing table "${name}" needed for D1 FK-cascade`);
|
|
50
|
+
return d;
|
|
51
|
+
};
|
|
52
|
+
const temp = (name) => TEMP_TABLE_PREFIX + name;
|
|
53
|
+
const stmts = [];
|
|
54
|
+
// Defer FK enforcement to commit — the D1-legal alternative to the (no-op) OFF bracket.
|
|
55
|
+
stmts.push("PRAGMA defer_foreign_keys = ON;");
|
|
56
|
+
// CREATE temps. FK targets inside the affected set are rewritten to their temp
|
|
57
|
+
// name (forward-refs are fine — SQLite resolves FK targets lazily); targets
|
|
58
|
+
// outside the set keep their real name.
|
|
59
|
+
for (const t of order) {
|
|
60
|
+
const src = expectedTable(t);
|
|
61
|
+
const clone = {
|
|
62
|
+
...src,
|
|
63
|
+
name: temp(t),
|
|
64
|
+
foreignKeys: src.foreignKeys.map((fk) => affected.has(fk.refTable) ? { ...fk, refTable: temp(fk.refTable) } : fk),
|
|
65
|
+
};
|
|
66
|
+
stmts.push(renderCreateTable(clone));
|
|
67
|
+
}
|
|
68
|
+
// INSERT carried columns. A referrer-only table has no changes → carry every
|
|
69
|
+
// expected column (computeCarryColumns([], …)).
|
|
70
|
+
for (const t of order) {
|
|
71
|
+
const tableChanges = changes.filter((c) => changeTable(c) === t);
|
|
72
|
+
const { insertCols, selectCols } = computeCarryColumns(tableChanges, expectedTable(t));
|
|
73
|
+
if (insertCols.length > 0) {
|
|
74
|
+
stmts.push(`INSERT INTO ${quote(temp(t))} (${insertCols.map(quote).join(", ")}) ` +
|
|
75
|
+
`SELECT ${selectCols.map(quote).join(", ")} FROM ${quote(t)};`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// DROP referrers-first (reverse topological order).
|
|
79
|
+
for (const t of [...order].reverse()) {
|
|
80
|
+
stmts.push(`DROP TABLE ${quote(t)};`);
|
|
81
|
+
}
|
|
82
|
+
// RENAME parents-first; recreate each table's indexes immediately after rename.
|
|
83
|
+
for (const t of order) {
|
|
84
|
+
stmts.push(`ALTER TABLE ${quote(temp(t))} RENAME TO ${quote(t)};`);
|
|
85
|
+
for (const ix of expectedTable(t).indexes) {
|
|
86
|
+
stmts.push(renderCreateIndex(t, ix));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const up = stmts.join("\n\n");
|
|
90
|
+
// Best-effort down, mirroring renderRecreate's WARNING block.
|
|
91
|
+
const downWarning = [
|
|
92
|
+
`-- WARNING: SQLite recreate-and-copy down migration is best-effort.`,
|
|
93
|
+
`-- Reverse the column type/nullable/default changes by hand if needed.`,
|
|
94
|
+
`-- Dropped data cannot be restored.`,
|
|
95
|
+
].join("\n");
|
|
96
|
+
return { up, downWarning, affected };
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=d1-cascade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1-cascade.js","sourceRoot":"","sources":["../../src/emit/d1-cascade.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,4 +19,14 @@ export declare class D1ReferencedTableRebuildError extends Error {
|
|
|
19
19
|
readonly refusals: D1RebuildRefusal[];
|
|
20
20
|
constructor(refusals: D1RebuildRefusal[]);
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Thrown when a D1 FK-cascade rebuild (#241) cannot be ordered because the
|
|
24
|
+
* affected tables form a multi-table foreign-key cycle. Unlike the acyclic case
|
|
25
|
+
* (which the cascade emitter rebuilds via `PRAGMA defer_foreign_keys = ON`), a
|
|
26
|
+
* cycle has no parents-first order, so the rebuild is refused at generation time.
|
|
27
|
+
*/
|
|
28
|
+
export declare class D1CyclicForeignKeyError extends Error {
|
|
29
|
+
readonly cycle: string[];
|
|
30
|
+
constructor(cycle: string[]);
|
|
31
|
+
}
|
|
22
32
|
//# sourceMappingURL=d1-fk-refuse.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"d1-fk-refuse.d.ts","sourceRoot":"","sources":["../../src/emit/d1-fk-refuse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,EACpC,cAAc,EAAE,cAAc,GAC7B,gBAAgB,EAAE,CASpB;AAED,0FAA0F;AAC1F,qBAAa,6BAA8B,SAAQ,KAAK;aAC1B,QAAQ,EAAE,gBAAgB,EAAE;gBAA5B,QAAQ,EAAE,gBAAgB,EAAE;CAIzD"}
|
|
1
|
+
{"version":3,"file":"d1-fk-refuse.d.ts","sourceRoot":"","sources":["../../src/emit/d1-fk-refuse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,EACpC,cAAc,EAAE,cAAc,GAC7B,gBAAgB,EAAE,CASpB;AAED,0FAA0F;AAC1F,qBAAa,6BAA8B,SAAQ,KAAK;aAC1B,QAAQ,EAAE,gBAAgB,EAAE;gBAA5B,QAAQ,EAAE,gBAAgB,EAAE;CAIzD;AAED;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aACpB,KAAK,EAAE,MAAM,EAAE;gBAAf,KAAK,EAAE,MAAM,EAAE;CAI5C"}
|
|
@@ -25,6 +25,31 @@ export class D1ReferencedTableRebuildError extends Error {
|
|
|
25
25
|
this.name = "D1ReferencedTableRebuildError";
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Thrown when a D1 FK-cascade rebuild (#241) cannot be ordered because the
|
|
30
|
+
* affected tables form a multi-table foreign-key cycle. Unlike the acyclic case
|
|
31
|
+
* (which the cascade emitter rebuilds via `PRAGMA defer_foreign_keys = ON`), a
|
|
32
|
+
* cycle has no parents-first order, so the rebuild is refused at generation time.
|
|
33
|
+
*/
|
|
34
|
+
export class D1CyclicForeignKeyError extends Error {
|
|
35
|
+
cycle;
|
|
36
|
+
constructor(cycle) {
|
|
37
|
+
super(formatCycleMessage(cycle));
|
|
38
|
+
this.cycle = cycle;
|
|
39
|
+
this.name = "D1CyclicForeignKeyError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function formatCycleMessage(cycle) {
|
|
43
|
+
const members = cycle.map((n) => `"${n}"`).join(", ");
|
|
44
|
+
return (`Cannot rebuild the following table(s) on Cloudflare D1 — their foreign keys form a ` +
|
|
45
|
+
`cycle: ${members}.\n\n` +
|
|
46
|
+
`The FK-cascade rebuild recipe recreates tables parents-first, which is impossible ` +
|
|
47
|
+
`when tables reference each other in a cycle. Even with ` +
|
|
48
|
+
"`PRAGMA defer_foreign_keys = ON`" +
|
|
49
|
+
` the DROP/RENAME sequence cannot be ordered to keep every reference valid. To apply ` +
|
|
50
|
+
`this on D1, hand-write the migration (drop the foreign key on one side of the cycle, ` +
|
|
51
|
+
`rebuild the tables, then restore it), or break the cycle in your metadata.`);
|
|
52
|
+
}
|
|
28
53
|
function formatMessage(refusals) {
|
|
29
54
|
const lines = refusals.map((r) => {
|
|
30
55
|
const refs = r.referencedBy.map((n) => `"${n}"`).join(", ");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"d1-fk-refuse.js","sourceRoot":"","sources":["../../src/emit/d1-fk-refuse.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,eAAoC,EACpC,cAA8B;IAE9B,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;QAChC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM;aACvC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC;aAChE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,0FAA0F;AAC1F,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IAC1B;IAA5B,YAA4B,QAA4B;QACtD,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;QADL,aAAQ,GAAR,QAAQ,CAAoB;QAEtD,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAC9C,CAAC;CACF;AAED,SAAS,aAAa,CAAC,QAA4B;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,OAAO,QAAQ,CAAC,CAAC,KAAK,yCAAyC,IAAI,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,OAAO,CACL,mFAAmF;QACnF,iBAAiB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;QACvC,6DAA6D;QAC7D,oFAAoF;QACpF,oFAAoF;QACpF,oFAAoF;QACpF,qFAAqF;QACrF,sFAAsF;QACtF,4DAA4D,CAC7D,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"d1-fk-refuse.js","sourceRoot":"","sources":["../../src/emit/d1-fk-refuse.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,eAAoC,EACpC,cAA8B;IAE9B,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;QAChC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM;aACvC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC;aAChE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,0FAA0F;AAC1F,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IAC1B;IAA5B,YAA4B,QAA4B;QACtD,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;QADL,aAAQ,GAAR,QAAQ,CAAoB;QAEtD,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAC9C,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACpB;IAA5B,YAA4B,KAAe;QACzC,KAAK,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;QADP,UAAK,GAAL,KAAK,CAAU;QAEzC,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,SAAS,kBAAkB,CAAC,KAAe;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,CACL,qFAAqF;QACrF,UAAU,OAAO,OAAO;QACxB,oFAAoF;QACpF,yDAAyD;QACzD,kCAAkC;QAClC,sFAAsF;QACtF,uFAAuF;QACvF,4EAA4E,CAC7E,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,QAA4B;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,OAAO,QAAQ,CAAC,CAAC,KAAK,yCAAyC,IAAI,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,OAAO,CACL,mFAAmF;QACnF,iBAAiB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;QACvC,6DAA6D;QAC7D,oFAAoF;QACpF,oFAAoF;QACpF,oFAAoF;QACpF,qFAAqF;QACrF,sFAAsF;QACtF,4DAA4D,CAC7D,CAAC;AACJ,CAAC"}
|
package/dist/emit/d1.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { Change, EmitResult, SchemaSnapshot, SnapshotMeta } from "../types.js";
|
|
2
|
-
export declare function renderD1(changes: readonly Change[], expectedSchema?: SchemaSnapshot, actualMeta?: SnapshotMeta
|
|
2
|
+
export declare function renderD1(changes: readonly Change[], expectedSchema?: SchemaSnapshot, actualMeta?: SnapshotMeta,
|
|
3
|
+
/** The actual (introspected) DB schema — enables the #241 FK-cascade rebuild. */
|
|
4
|
+
actualSchema?: SchemaSnapshot): EmitResult;
|
|
3
5
|
//# sourceMappingURL=d1.d.ts.map
|
package/dist/emit/d1.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/emit/d1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../src/emit/d1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAapF,wBAAgB,QAAQ,CACtB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,YAAY;AACzB,iFAAiF;AACjF,YAAY,CAAC,EAAE,cAAc,GAC5B,UAAU,CA8DZ"}
|
package/dist/emit/d1.js
CHANGED
|
@@ -1,22 +1,61 @@
|
|
|
1
|
-
import { renderSqlite } from "./sqlite.js";
|
|
1
|
+
import { renderSqlite, changeTable } from "./sqlite.js";
|
|
2
2
|
import { applyD1SafetyPass } from "./d1-safety-pass.js";
|
|
3
|
-
import { findReferencedRebuilds, D1ReferencedTableRebuildError } from "./d1-fk-refuse.js";
|
|
4
|
-
|
|
3
|
+
import { findReferencedRebuilds, D1ReferencedTableRebuildError, D1CyclicForeignKeyError, } from "./d1-fk-refuse.js";
|
|
4
|
+
import { buildFkEdges, unionEdges } from "./fk-graph.js";
|
|
5
|
+
import { emitD1Cascade } from "./d1-cascade.js";
|
|
6
|
+
const EMPTY_SCHEMA = { tables: [], views: [] };
|
|
7
|
+
export function renderD1(changes, expectedSchema, actualMeta,
|
|
8
|
+
/** The actual (introspected) DB schema — enables the #241 FK-cascade rebuild. */
|
|
9
|
+
actualSchema) {
|
|
5
10
|
const sqliteResult = renderSqlite(changes, expectedSchema, actualMeta);
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// D1
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Trigger detection: is any recreated table the target of a foreign key in the
|
|
12
|
+
// expected OR actual schema (a self-reference counts)? Only such rebuilds are
|
|
13
|
+
// un-appliable on D1 — the recipe's `PRAGMA foreign_keys = OFF` is a no-op
|
|
14
|
+
// inside D1's implicit transaction, so `DROP TABLE <referenced>` fails (#226).
|
|
15
|
+
const edges = unionEdges(buildFkEdges(expectedSchema ?? EMPTY_SCHEMA), actualSchema ? buildFkEdges(actualSchema) : new Map());
|
|
16
|
+
const isReferenced = (t) => {
|
|
17
|
+
for (const parents of edges.values()) {
|
|
18
|
+
if (parents.has(t))
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
const referenced = [...sqliteResult.recreatedTables].filter(isReferenced);
|
|
24
|
+
// No referenced rebuild → byte-identical to the pre-#241 path.
|
|
25
|
+
if (referenced.length === 0) {
|
|
26
|
+
return {
|
|
27
|
+
up: applyD1SafetyPass(sqliteResult.up),
|
|
28
|
+
down: applyD1SafetyPass(sqliteResult.down),
|
|
29
|
+
recreatedTables: sqliteResult.recreatedTables,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// A referenced rebuild exists. Without the actual schema we cannot prove a
|
|
33
|
+
// cascade is safe, so refuse exactly as #226 does — never emit an unproven one.
|
|
34
|
+
if (actualSchema === undefined) {
|
|
35
|
+
throw new D1ReferencedTableRebuildError(findReferencedRebuilds(sqliteResult.recreatedTables, expectedSchema ?? EMPTY_SCHEMA));
|
|
36
|
+
}
|
|
37
|
+
// recreatedTables non-empty ⇒ renderSqlite already guaranteed expectedSchema.
|
|
38
|
+
const cascade = emitD1Cascade(changes, expectedSchema, actualSchema, sqliteResult.recreatedTables);
|
|
39
|
+
if ("refuseCycle" in cascade) {
|
|
40
|
+
throw new D1CyclicForeignKeyError(cascade.refuseCycle);
|
|
15
41
|
}
|
|
42
|
+
// Splice: the affected set is rebuilt by the cascade; every other change flows
|
|
43
|
+
// through the native path, emitted AFTER the cascade so renamed parents exist
|
|
44
|
+
// before any native CREATE TABLE referencing them. `defer_foreign_keys = ON`
|
|
45
|
+
// persists across the whole implicit transaction, so FK checks defer to commit
|
|
46
|
+
// where the final state is consistent.
|
|
47
|
+
const { up: cascadeUp, downWarning, affected } = cascade;
|
|
48
|
+
const nonAffected = changes.filter((c) => {
|
|
49
|
+
const t = changeTable(c);
|
|
50
|
+
return !(t !== undefined && affected.has(t));
|
|
51
|
+
});
|
|
52
|
+
const rest = renderSqlite(nonAffected, expectedSchema, actualMeta);
|
|
53
|
+
const up = [cascadeUp, rest.up].filter((s) => s.length > 0).join("\n\n");
|
|
54
|
+
const down = [rest.down, downWarning].filter((s) => s.length > 0).join("\n\n");
|
|
16
55
|
return {
|
|
17
|
-
up: applyD1SafetyPass(
|
|
18
|
-
down: applyD1SafetyPass(
|
|
19
|
-
recreatedTables:
|
|
56
|
+
up: applyD1SafetyPass(up),
|
|
57
|
+
down: applyD1SafetyPass(down),
|
|
58
|
+
recreatedTables: new Set([...affected, ...rest.recreatedTables]),
|
|
20
59
|
};
|
|
21
60
|
}
|
|
22
61
|
//# sourceMappingURL=d1.js.map
|
package/dist/emit/d1.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"d1.js","sourceRoot":"","sources":["../../src/emit/d1.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"d1.js","sourceRoot":"","sources":["../../src/emit/d1.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SchemaSnapshot } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pure, I/O-free graph core the D1 FK-cascade emitter orders rebuilds by. Edges
|
|
4
|
+
* point child→parent (a table → the table(s) its foreign keys reference),
|
|
5
|
+
* self-loops included for a self-referential table.
|
|
6
|
+
*/
|
|
7
|
+
/** Builds child→parent FK edges from a schema. Self-loops included. */
|
|
8
|
+
export declare function buildFkEdges(schema: SchemaSnapshot): Map<string, Set<string>>;
|
|
9
|
+
/** Merges two edge maps into a new one; neither input is mutated. */
|
|
10
|
+
export declare function unionEdges(a: Map<string, Set<string>>, b: Map<string, Set<string>>): Map<string, Set<string>>;
|
|
11
|
+
/**
|
|
12
|
+
* `recreated` plus every transitive *referrer* — walk edges backwards: any
|
|
13
|
+
* table with an edge into the current set (i.e. it references a member of the
|
|
14
|
+
* set) joins the set, repeated to a fixpoint.
|
|
15
|
+
*/
|
|
16
|
+
export declare function affectedSet(recreated: ReadonlySet<string>, edges: Map<string, Set<string>>): Set<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Kahn's algorithm over `nodes`, treating an edge `child→parent` as "parent
|
|
19
|
+
* must come before child". Self-loops (`x→x`) and edges whose target is not
|
|
20
|
+
* in `nodes` are dropped before ordering. `order` is parents-first. If nodes
|
|
21
|
+
* remain when the queue empties, the sort cannot complete — those remaining
|
|
22
|
+
* nodes (a multi-node cycle) are returned as `cycle` instead.
|
|
23
|
+
*/
|
|
24
|
+
export declare function topoOrder(nodes: Set<string>, edges: Map<string, Set<string>>): {
|
|
25
|
+
order: string[];
|
|
26
|
+
cycle: string[] | null;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=fk-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fk-graph.d.ts","sourceRoot":"","sources":["../../src/emit/fk-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;GAIG;AAEH,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAU7E;AAED,qEAAqE;AACrE,wBAAgB,UAAU,CACxB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAC3B,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC1B,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAc1B;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAC9B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC9B,GAAG,CAAC,MAAM,CAAC,CAiBb;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,EAClB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAC9B;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CAAE,CA8C7C"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, I/O-free graph core the D1 FK-cascade emitter orders rebuilds by. Edges
|
|
3
|
+
* point child→parent (a table → the table(s) its foreign keys reference),
|
|
4
|
+
* self-loops included for a self-referential table.
|
|
5
|
+
*/
|
|
6
|
+
/** Builds child→parent FK edges from a schema. Self-loops included. */
|
|
7
|
+
export function buildFkEdges(schema) {
|
|
8
|
+
const edges = new Map();
|
|
9
|
+
for (const t of schema.tables) {
|
|
10
|
+
const parents = new Set();
|
|
11
|
+
for (const fk of t.foreignKeys) {
|
|
12
|
+
parents.add(fk.refTable);
|
|
13
|
+
}
|
|
14
|
+
edges.set(t.name, parents);
|
|
15
|
+
}
|
|
16
|
+
return edges;
|
|
17
|
+
}
|
|
18
|
+
/** Merges two edge maps into a new one; neither input is mutated. */
|
|
19
|
+
export function unionEdges(a, b) {
|
|
20
|
+
const merged = new Map();
|
|
21
|
+
for (const [node, parents] of a) {
|
|
22
|
+
merged.set(node, new Set(parents));
|
|
23
|
+
}
|
|
24
|
+
for (const [node, parents] of b) {
|
|
25
|
+
const existing = merged.get(node);
|
|
26
|
+
if (existing) {
|
|
27
|
+
for (const p of parents)
|
|
28
|
+
existing.add(p);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
merged.set(node, new Set(parents));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* `recreated` plus every transitive *referrer* — walk edges backwards: any
|
|
38
|
+
* table with an edge into the current set (i.e. it references a member of the
|
|
39
|
+
* set) joins the set, repeated to a fixpoint.
|
|
40
|
+
*/
|
|
41
|
+
export function affectedSet(recreated, edges) {
|
|
42
|
+
const affected = new Set(recreated);
|
|
43
|
+
let changed = true;
|
|
44
|
+
while (changed) {
|
|
45
|
+
changed = false;
|
|
46
|
+
for (const [node, parents] of edges) {
|
|
47
|
+
if (affected.has(node))
|
|
48
|
+
continue;
|
|
49
|
+
for (const p of parents) {
|
|
50
|
+
if (affected.has(p)) {
|
|
51
|
+
affected.add(node);
|
|
52
|
+
changed = true;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return affected;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Kahn's algorithm over `nodes`, treating an edge `child→parent` as "parent
|
|
62
|
+
* must come before child". Self-loops (`x→x`) and edges whose target is not
|
|
63
|
+
* in `nodes` are dropped before ordering. `order` is parents-first. If nodes
|
|
64
|
+
* remain when the queue empties, the sort cannot complete — those remaining
|
|
65
|
+
* nodes (a multi-node cycle) are returned as `cycle` instead.
|
|
66
|
+
*/
|
|
67
|
+
export function topoOrder(nodes, edges) {
|
|
68
|
+
// childOf(parent) = children within `nodes` that must be emitted after `parent`.
|
|
69
|
+
const childrenOf = new Map();
|
|
70
|
+
const inDegree = new Map();
|
|
71
|
+
for (const n of nodes) {
|
|
72
|
+
childrenOf.set(n, new Set());
|
|
73
|
+
inDegree.set(n, 0);
|
|
74
|
+
}
|
|
75
|
+
for (const n of nodes) {
|
|
76
|
+
const parents = edges.get(n) ?? new Set();
|
|
77
|
+
for (const p of parents) {
|
|
78
|
+
if (p === n)
|
|
79
|
+
continue; // drop self-loop
|
|
80
|
+
if (!nodes.has(p))
|
|
81
|
+
continue; // drop edge leaving `nodes`
|
|
82
|
+
const kids = childrenOf.get(p);
|
|
83
|
+
if (kids === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
if (kids.has(n))
|
|
86
|
+
continue; // already recorded (dedupe parallel edges)
|
|
87
|
+
kids.add(n);
|
|
88
|
+
inDegree.set(n, (inDegree.get(n) ?? 0) + 1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Stable order: iterate `nodes` in insertion order for the initial queue.
|
|
92
|
+
const queue = [];
|
|
93
|
+
for (const n of nodes) {
|
|
94
|
+
if (inDegree.get(n) === 0)
|
|
95
|
+
queue.push(n);
|
|
96
|
+
}
|
|
97
|
+
const order = [];
|
|
98
|
+
let head = 0;
|
|
99
|
+
while (head < queue.length) {
|
|
100
|
+
const n = queue[head++];
|
|
101
|
+
if (n === undefined)
|
|
102
|
+
continue;
|
|
103
|
+
order.push(n);
|
|
104
|
+
for (const child of childrenOf.get(n) ?? []) {
|
|
105
|
+
const remaining = (inDegree.get(child) ?? 0) - 1;
|
|
106
|
+
inDegree.set(child, remaining);
|
|
107
|
+
if (remaining === 0)
|
|
108
|
+
queue.push(child);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (order.length < nodes.size) {
|
|
112
|
+
const remaining = [...nodes].filter((n) => !order.includes(n));
|
|
113
|
+
return { order: [], cycle: remaining };
|
|
114
|
+
}
|
|
115
|
+
return { order, cycle: null };
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=fk-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fk-graph.js","sourceRoot":"","sources":["../../src/emit/fk-graph.ts"],"names":[],"mappings":"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"}
|
package/dist/emit/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface EmitOptions {
|
|
|
12
12
|
* fall back to recreate-and-copy. Unknown/absent version → assume modern.
|
|
13
13
|
*/
|
|
14
14
|
actualMeta?: SnapshotMeta;
|
|
15
|
+
/** Used by the d1 cascade emitter to build the actual∪expected FK graph. */
|
|
16
|
+
actualSchema?: SchemaSnapshot;
|
|
15
17
|
}
|
|
16
18
|
export declare function emit(changes: Change[], opts: EmitOptions): EmitResult;
|
|
17
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/emit/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/emit/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS7F,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/emit/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS7F,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,cAAc,CAAC;CAC/B;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CASrE"}
|
package/dist/emit/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export function emit(changes, opts) {
|
|
|
9
9
|
switch (opts.dialect) {
|
|
10
10
|
case "postgres": return renderPostgres(changes);
|
|
11
11
|
case "sqlite": return renderSqlite(changes, opts.expectedSchema, opts.actualMeta);
|
|
12
|
-
case "d1": return renderD1(changes, opts.expectedSchema, opts.actualMeta);
|
|
12
|
+
case "d1": return renderD1(changes, opts.expectedSchema, opts.actualMeta, opts.actualSchema);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
//# sourceMappingURL=index.js.map
|
package/dist/emit/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/emit/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/emit/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAsBnC,MAAM,UAAU,IAAI,CAAC,OAAiB,EAAE,IAAiB;IACvD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IACpE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAE/D,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,KAAK,UAAU,CAAC,CAAC,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;QAChD,KAAK,QAAQ,CAAC,CAAG,OAAO,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACpF,KAAK,IAAI,CAAC,CAAO,OAAO,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACrG,CAAC;AACH,CAAC"}
|
package/dist/emit/sqlite.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
import type { Change, EmitResult, SchemaSnapshot, SnapshotMeta } from "../types.js";
|
|
1
|
+
import type { Change, EmitResult, IndexDescriptor, TableDescriptor, SchemaSnapshot, SnapshotMeta } from "../types.js";
|
|
2
|
+
export interface CarryColumns {
|
|
3
|
+
insertCols: string[];
|
|
4
|
+
selectCols: string[];
|
|
5
|
+
}
|
|
2
6
|
export declare function renderSqlite(changes: readonly Change[], expectedSchema?: SchemaSnapshot, actualMeta?: SnapshotMeta): EmitResult;
|
|
7
|
+
/** The table a change targets, or undefined for view-scoped changes. Exported for the D1 FK-cascade emitter (read-only). */
|
|
8
|
+
export declare function changeTable(c: Change): string | undefined;
|
|
9
|
+
/** newTable columns not newly-added, mapped to their old-name SELECT source (for renames). */
|
|
10
|
+
export declare function computeCarryColumns(tableChanges: Change[], newTable: TableDescriptor): CarryColumns;
|
|
11
|
+
export declare function renderCreateTable(t: TableDescriptor): string;
|
|
12
|
+
export declare function renderCreateIndex(table: string, ix: IndexDescriptor): string;
|
|
13
|
+
/** SQLite identifier quoter (`"id"`). Exported for the D1 FK-cascade emitter (read-only). */
|
|
14
|
+
export declare function quote(ident: string): string;
|
|
3
15
|
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/emit/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/emit/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAAE,UAAU,EAAoB,eAAe,EACrD,eAAe,EAAE,cAAc,EAAE,YAAY,EAC9C,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,YAAY;IAAG,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;CAAE;AA+B7E,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,YAAY,GACxB,UAAU,CAmEZ;AAED,4HAA4H;AAC5H,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAqBzD;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,eAAe,GAAG,YAAY,CASnG;AA4GD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAyB5D;AAqFD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,GAAG,MAAM,CAoB5E;AAED,6FAA6F;AAC7F,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG3C"}
|
package/dist/emit/sqlite.js
CHANGED
|
@@ -92,7 +92,8 @@ export function renderSqlite(changes, expectedSchema, actualMeta) {
|
|
|
92
92
|
recreatedTables: recreateTables,
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
/** The table a change targets, or undefined for view-scoped changes. Exported for the D1 FK-cascade emitter (read-only). */
|
|
96
|
+
export function changeTable(c) {
|
|
96
97
|
switch (c.kind) {
|
|
97
98
|
case "create-table": return c.table.name;
|
|
98
99
|
case "drop-table": return c.table;
|
|
@@ -114,27 +115,24 @@ function changeTable(c) {
|
|
|
114
115
|
return undefined;
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
/** newTable columns not newly-added, mapped to their old-name SELECT source (for renames). */
|
|
119
|
+
export function computeCarryColumns(tableChanges, newTable) {
|
|
119
120
|
const renames = new Map();
|
|
120
|
-
for (const c of tableChanges)
|
|
121
|
+
for (const c of tableChanges)
|
|
121
122
|
if (c.kind === "rename-column")
|
|
122
123
|
renames.set(c.from, c.to);
|
|
123
|
-
}
|
|
124
|
-
// Columns added in this migration don't exist in old table — exclude from SELECT.
|
|
125
124
|
const addedNames = new Set();
|
|
126
|
-
for (const c of tableChanges)
|
|
125
|
+
for (const c of tableChanges)
|
|
127
126
|
if (c.kind === "add-column")
|
|
128
127
|
addedNames.add(c.column.name);
|
|
129
|
-
}
|
|
130
|
-
// Reverse map: new_name → old_name (for SELECT source column lookup).
|
|
131
128
|
const renamesReverse = new Map();
|
|
132
129
|
for (const [from, to] of renames)
|
|
133
130
|
renamesReverse.set(to, from);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
const carry = newTable.columns.filter((c) => !addedNames.has(c.name));
|
|
132
|
+
return { insertCols: carry.map((c) => c.name), selectCols: carry.map((c) => renamesReverse.get(c.name) ?? c.name) };
|
|
133
|
+
}
|
|
134
|
+
function renderRecreate(table, tableChanges, newTable) {
|
|
135
|
+
const { insertCols, selectCols } = computeCarryColumns(tableChanges, newTable);
|
|
138
136
|
// Build the new-table CREATE using temp name.
|
|
139
137
|
const tmp = `__new_${table}`;
|
|
140
138
|
const tmpDescriptor = { ...newTable, name: tmp };
|
|
@@ -226,7 +224,7 @@ function renderDownNative(c) {
|
|
|
226
224
|
case "replace-view": return `-- WARNING: down migration cannot restore the original view definition`;
|
|
227
225
|
}
|
|
228
226
|
}
|
|
229
|
-
function renderCreateTable(t) {
|
|
227
|
+
export function renderCreateTable(t) {
|
|
230
228
|
const compositePk = t.primaryKey.length > 1;
|
|
231
229
|
const colDefs = t.columns.map((c) => {
|
|
232
230
|
const isSinglePk = !compositePk && t.primaryKey[0] === c.name;
|
|
@@ -341,7 +339,7 @@ function renderDefault(d, t) {
|
|
|
341
339
|
return quoted;
|
|
342
340
|
}
|
|
343
341
|
}
|
|
344
|
-
function renderCreateIndex(table, ix) {
|
|
342
|
+
export function renderCreateIndex(table, ix) {
|
|
345
343
|
const u = ix.unique ? "UNIQUE " : "";
|
|
346
344
|
// SQLite natively supports expression indexes, per-column DESC, and partial
|
|
347
345
|
// (WHERE) indexes — render all three. Dropping them is not an option:
|
|
@@ -362,7 +360,8 @@ function renderCreateIndex(table, ix) {
|
|
|
362
360
|
const where = ix.where ? ` WHERE (${ix.where})` : "";
|
|
363
361
|
return `CREATE ${u}INDEX ${quote(ix.name)} ON ${quote(table)} (${keys})${where};`;
|
|
364
362
|
}
|
|
365
|
-
|
|
363
|
+
/** SQLite identifier quoter (`"id"`). Exported for the D1 FK-cascade emitter (read-only). */
|
|
364
|
+
export function quote(ident) {
|
|
366
365
|
if (ident.includes('"'))
|
|
367
366
|
throw new Error(`unsafe identifier: ${ident}`);
|
|
368
367
|
return `"${ident}"`;
|