@happyvertical/smrt-core 0.40.68 → 0.40.70
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/AGENTS.md +29 -4
- package/README.md +20 -1
- package/agents/query-bounds.md +45 -0
- package/agents/schema-paths.md +328 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +2 -1
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +28 -4
- package/dist/class.js.map +1 -1
- package/dist/collection.d.ts +101 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +224 -43
- package/dist/collection.js.map +1 -1
- package/dist/database.d.ts +28 -0
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +16 -8
- package/dist/database.js.map +1 -1
- package/dist/db-errors.d.ts +105 -0
- package/dist/db-errors.d.ts.map +1 -0
- package/dist/db-errors.js +382 -0
- package/dist/db-errors.js.map +1 -0
- package/dist/decorators/index.d.ts +17 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +65 -7
- package/dist/decorators/index.js.map +1 -1
- package/dist/dispatch/bus.d.ts.map +1 -1
- package/dist/dispatch/bus.js +4 -3
- package/dist/dispatch/bus.js.map +1 -1
- package/dist/errors.d.ts +30 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +34 -2
- package/dist/errors.js.map +1 -1
- package/dist/generators/changes-route.d.ts.map +1 -1
- package/dist/generators/changes-route.js +6 -3
- package/dist/generators/changes-route.js.map +1 -1
- package/dist/generators/mcp-runtime-template.d.ts +8 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +38 -4
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +16 -0
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +41 -3
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/rest.d.ts +22 -0
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +34 -3
- package/dist/generators/rest.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts +202 -8
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +604 -46
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/generator.d.ts +31 -4
- package/dist/migrations/generator.d.ts.map +1 -1
- package/dist/migrations/generator.js +49 -5
- package/dist/migrations/generator.js.map +1 -1
- package/dist/migrations/index.d.ts +3 -2
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +5 -3
- package/dist/migrations/orchestrate.js +35 -6
- package/dist/migrations/orchestrate.js.map +1 -1
- package/dist/migrations/sqlite-rebuild.d.ts +142 -0
- package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
- package/dist/migrations/sqlite-rebuild.js +514 -0
- package/dist/migrations/sqlite-rebuild.js.map +1 -0
- package/dist/migrations/tracker.d.ts +114 -1
- package/dist/migrations/tracker.d.ts.map +1 -1
- package/dist/migrations/tracker.js +331 -16
- package/dist/migrations/tracker.js.map +1 -1
- package/dist/migrations/types.d.ts +19 -4
- package/dist/migrations/types.d.ts.map +1 -1
- package/dist/migrations.js +5 -3
- package/dist/object.d.ts +45 -6
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +60 -13
- package/dist/object.js.map +1 -1
- package/dist/postgres-timeouts.d.ts +240 -0
- package/dist/postgres-timeouts.d.ts.map +1 -0
- package/dist/postgres-timeouts.js +204 -0
- package/dist/postgres-timeouts.js.map +1 -0
- package/dist/query-bounds.d.ts +97 -0
- package/dist/query-bounds.d.ts.map +1 -0
- package/dist/query-bounds.js +173 -0
- package/dist/query-bounds.js.map +1 -0
- package/dist/registry/types.d.ts +32 -1
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.js +1 -1
- package/dist/scanner/types.d.ts +5 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/base-strategy.js +2 -2
- package/dist/schema/ddl/base-strategy.js.map +1 -1
- package/dist/schema/generator.d.ts +70 -5
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +124 -6
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +4 -1
- package/dist/schema/live-parity.d.ts +90 -0
- package/dist/schema/live-parity.d.ts.map +1 -0
- package/dist/schema/live-parity.js +600 -0
- package/dist/schema/live-parity.js.map +1 -0
- package/dist/schema/manifest-schema.d.ts +121 -0
- package/dist/schema/manifest-schema.d.ts.map +1 -0
- package/dist/schema/manifest-schema.js +267 -0
- package/dist/schema/manifest-schema.js.map +1 -0
- package/dist/schema/schema-aggregator.d.ts +24 -10
- package/dist/schema/schema-aggregator.d.ts.map +1 -1
- package/dist/schema/schema-aggregator.js +35 -90
- package/dist/schema/schema-aggregator.js.map +1 -1
- package/dist/schema/system-table-shapes.d.ts +52 -0
- package/dist/schema/system-table-shapes.d.ts.map +1 -0
- package/dist/schema/system-table-shapes.js +174 -0
- package/dist/schema/system-table-shapes.js.map +1 -0
- package/dist/schema/types.d.ts +103 -4
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts +1 -0
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +3 -1
- package/dist/schema/utils.js.map +1 -1
- package/dist/schema.js +4 -1
- package/dist/smrt-knowledge.json +18 -6
- package/dist/sync/apply.d.ts.map +1 -1
- package/dist/sync/apply.js +9 -16
- package/dist/sync/apply.js.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +1 -0
- package/dist/testing/database.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +97 -13
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +6 -4
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +11 -11
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { quoteIdentifier } from "../schema/sql-identifiers.js";
|
|
2
|
+
import { createLogger } from "@happyvertical/logger";
|
|
3
|
+
//#region src/migrations/sqlite-rebuild.ts
|
|
4
|
+
/**
|
|
5
|
+
* SQLite table-rebuild migrations (issue #2370)
|
|
6
|
+
*
|
|
7
|
+
* SQLite has no `ALTER TABLE ... ALTER COLUMN ... TYPE`. Before this module
|
|
8
|
+
* every type-bucket change on SQLite (the canonical one being a numeric
|
|
9
|
+
* default edited from `0` to `0.0`, i.e. INTEGER → REAL) turned into an
|
|
10
|
+
* advisory differ comment, which `db:migrate` classifies as
|
|
11
|
+
* *manual intervention* and exits 1 for — forever, because nothing in the
|
|
12
|
+
* toolchain could ever perform the change.
|
|
13
|
+
*
|
|
14
|
+
* This module turns that comment into the executable rebuild SQLite's own
|
|
15
|
+
* documentation prescribes ("Making Other Kinds Of Table Schema Changes",
|
|
16
|
+
* lang_altertable.html):
|
|
17
|
+
*
|
|
18
|
+
* 1. `PRAGMA defer_foreign_keys = ON` — defers foreign-key enforcement to
|
|
19
|
+
* COMMIT so the window where the table does not exist cannot raise. It is
|
|
20
|
+
* honored *inside* a transaction (unlike `PRAGMA foreign_keys`, which is a
|
|
21
|
+
* silent no-op there) and resets itself at COMMIT/ROLLBACK.
|
|
22
|
+
* 2. Create a new table under a reserved `_smrt_rebuild_` name carrying the
|
|
23
|
+
* target shape.
|
|
24
|
+
* 3. Copy every row across.
|
|
25
|
+
* 4. Drop the original table.
|
|
26
|
+
* 5. Rename the new table into its place, with `PRAGMA legacy_alter_table`
|
|
27
|
+
* ON for the rename itself (see {@link buildSqliteRebuildStatements}).
|
|
28
|
+
* 6. Recreate the indexes and triggers that died with the original table.
|
|
29
|
+
*
|
|
30
|
+
* The whole plan is a plain statement list, so it is executed by the
|
|
31
|
+
* `MigrationTracker` inside the same transaction as the rest of the batch:
|
|
32
|
+
* a failure anywhere rolls the table back to its original shape and contents.
|
|
33
|
+
*
|
|
34
|
+
* ## The target shape comes from the live DDL, not from the manifest
|
|
35
|
+
*
|
|
36
|
+
* The new table is the *original* `CREATE TABLE` text with only the drifted
|
|
37
|
+
* columns' type tokens replaced. That deliberately preserves everything the
|
|
38
|
+
* manifest does not model or does not own here: columns the manifest has
|
|
39
|
+
* dropped (SMRT is additive — a rebuild must not become an implicit
|
|
40
|
+
* `DROP COLUMN`), table-level constraints, `CHECK`s, collations, and table
|
|
41
|
+
* options such as `WITHOUT ROWID`. The `add_column` changes in the same batch
|
|
42
|
+
* still run their own `ALTER TABLE ... ADD COLUMN`, against the rebuilt table
|
|
43
|
+
* — {@link planSqliteTableRebuilds} hoists the rebuild ahead of them for
|
|
44
|
+
* exactly that reason.
|
|
45
|
+
*
|
|
46
|
+
* ## Why the copy carries no CAST
|
|
47
|
+
*
|
|
48
|
+
* The copy is a plain `INSERT INTO new SELECT ... FROM old`; SQLite applies
|
|
49
|
+
* the destination column's *affinity* on insert, which is exactly the
|
|
50
|
+
* conversion a fresh table would have performed (integer `1` becomes real
|
|
51
|
+
* `1.0` in a REAL column). An explicit `CAST` would be strictly worse: casting
|
|
52
|
+
* a non-numeric TEXT value to REAL/INTEGER silently yields `0`, and casting an
|
|
53
|
+
* ISO timestamp string to SQLite's NUMERIC-affinity `DATETIME` silently yields
|
|
54
|
+
* the leading year. Affinity conversion leaves values it cannot convert
|
|
55
|
+
* untouched instead of destroying them.
|
|
56
|
+
*
|
|
57
|
+
* ## When the rebuild refuses
|
|
58
|
+
*
|
|
59
|
+
* With `PRAGMA foreign_keys` enabled — which the SMRT SQLite adapter is by
|
|
60
|
+
* default — `DROP TABLE` performs an implicit `DELETE FROM` that fires
|
|
61
|
+
* `ON DELETE` actions on children. A child with `ON DELETE CASCADE` therefore
|
|
62
|
+
* loses its rows, and `defer_foreign_keys` does not help: it defers
|
|
63
|
+
* constraint *checks*, not FK *actions*. So when any table declares a foreign
|
|
64
|
+
* key onto the rebuild target and enforcement is on, the plan is refused and
|
|
65
|
+
* the change stays a manual intervention. That includes the target's own
|
|
66
|
+
* self-references, because the staging table copies them and so becomes a
|
|
67
|
+
* child of the table being dropped.
|
|
68
|
+
*/
|
|
69
|
+
var logger = createLogger({ level: "info" });
|
|
70
|
+
/**
|
|
71
|
+
* Prefix of the reserved staging table a rebuild creates. `_smrt_`-prefixed
|
|
72
|
+
* names are SMRT system tables, which the differ's dropped-table sweep skips,
|
|
73
|
+
* so a staging table left behind by a non-transactional adapter cannot be
|
|
74
|
+
* mistaken for user data.
|
|
75
|
+
*/
|
|
76
|
+
var SQLITE_REBUILD_TABLE_PREFIX = "_smrt_rebuild_";
|
|
77
|
+
/**
|
|
78
|
+
* The advisory SQL the differ emits for a SQLite type upgrade it cannot
|
|
79
|
+
* perform with an `ALTER TABLE`. `planSqliteTableRebuilds` replaces it with an
|
|
80
|
+
* executable plan; when it cannot, the comment survives and `db:migrate`
|
|
81
|
+
* reports the column as manual drift exactly as it did before #2370.
|
|
82
|
+
*/
|
|
83
|
+
function sqliteRebuildPlaceholderSql(columnName) {
|
|
84
|
+
return `-- SQLite: Type upgrade for ${quoteIdentifier(columnName)} requires table recreation`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* True when `sql` is the differ's "requires table recreation" placeholder —
|
|
88
|
+
* i.e. a type upgrade waiting for this module to turn it into a rebuild.
|
|
89
|
+
*/
|
|
90
|
+
function isSqliteRebuildPlaceholder(sql) {
|
|
91
|
+
return typeof sql === "string" && sql.includes("requires table recreation");
|
|
92
|
+
}
|
|
93
|
+
function blockedSql(columnName, reason) {
|
|
94
|
+
return `-- SQLite: ${quoteIdentifier(columnName)} requires a table rebuild, blocked: ${reason}`;
|
|
95
|
+
}
|
|
96
|
+
function coveredSql(columnName, rebuiltWith) {
|
|
97
|
+
return `-- SQLite: ${quoteIdentifier(columnName)} is rebuilt with ${quoteIdentifier(rebuiltWith)} in one table rebuild (no change needed)`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Replace every "requires table recreation" placeholder for `tableName` with
|
|
101
|
+
* one executable rebuild.
|
|
102
|
+
*
|
|
103
|
+
* All of the table's drifted columns are retyped by a *single* rebuild, so the
|
|
104
|
+
* first placeholder carries the plan and the rest become no-ops that reference
|
|
105
|
+
* it. Returns the changes unchanged when there is nothing to rebuild, and
|
|
106
|
+
* leaves the placeholders in place (annotated with the reason) when the
|
|
107
|
+
* rebuild cannot be planned safely — the pre-#2370 manual-intervention
|
|
108
|
+
* behavior.
|
|
109
|
+
*
|
|
110
|
+
* When a plan is produced, the rebuild is hoisted to the front of the table's
|
|
111
|
+
* changes. The plan's staging DDL and copy list are captured from the live
|
|
112
|
+
* schema *at diff time*, so a same-batch `ALTER TABLE ... ADD COLUMN` that ran
|
|
113
|
+
* first would be silently undone by the rebuild — both statements succeed, the
|
|
114
|
+
* transaction commits, and the new column is simply gone. The differ emits
|
|
115
|
+
* changes in manifest field order, which puts an `add_column` first whenever
|
|
116
|
+
* the new field is declared above the retyped one, so the order has to be
|
|
117
|
+
* imposed here rather than assumed. Rebuild first, then add columns to the
|
|
118
|
+
* rebuilt table.
|
|
119
|
+
*/
|
|
120
|
+
async function planSqliteTableRebuilds(options) {
|
|
121
|
+
const { db, tableName, changes, mapType } = options;
|
|
122
|
+
const pending = changes.filter((change) => change.type === "type_upgrade" && change.name && change.column && isSqliteRebuildPlaceholder(change.sql));
|
|
123
|
+
if (pending.length === 0) return changes;
|
|
124
|
+
const block = (reason) => {
|
|
125
|
+
logger.debug(`[SQLiteRebuild] Cannot rebuild ${tableName}: ${reason}; leaving ${pending.length} type upgrade(s) as manual drift`);
|
|
126
|
+
return changes.map((change) => pending.includes(change) ? {
|
|
127
|
+
...change,
|
|
128
|
+
sql: blockedSql(change.name, reason)
|
|
129
|
+
} : change);
|
|
130
|
+
};
|
|
131
|
+
let context;
|
|
132
|
+
try {
|
|
133
|
+
context = await readSqliteRebuildContext(db, tableName);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return block(`live schema introspection failed (${error instanceof Error ? error.message : String(error)})`);
|
|
136
|
+
}
|
|
137
|
+
if (!context.createTableSql) return block("no CREATE TABLE statement found in sqlite_master");
|
|
138
|
+
if (context.columns.length === 0) return block("PRAGMA table_info returned no columns");
|
|
139
|
+
if (context.foreignKeysEnabled && context.inboundReferences.length > 0) return block(`PRAGMA foreign_keys is ON and ${context.inboundReferences.map((name) => `"${name}"`).join(", ")} declare a foreign key onto this table, so DROP TABLE would fire their ON DELETE actions`);
|
|
140
|
+
const columnTypes = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const change of pending) columnTypes.set(change.name, mapType(change.column.type));
|
|
142
|
+
const plan = buildSqliteRebuildStatements({
|
|
143
|
+
tableName,
|
|
144
|
+
createTableSql: context.createTableSql,
|
|
145
|
+
columnTypes,
|
|
146
|
+
columns: context.columns,
|
|
147
|
+
objects: context.objects,
|
|
148
|
+
legacyAlterTable: context.legacyAlterTable
|
|
149
|
+
});
|
|
150
|
+
if (!plan) return block("the live CREATE TABLE statement could not be rewritten for the target column types");
|
|
151
|
+
const [primary, ...covered] = pending;
|
|
152
|
+
const rebuilt = changes.map((change) => {
|
|
153
|
+
if (change === primary) return {
|
|
154
|
+
...change,
|
|
155
|
+
sql: plan.statements[0],
|
|
156
|
+
sqlStatements: plan.statements
|
|
157
|
+
};
|
|
158
|
+
if (covered.includes(change)) {
|
|
159
|
+
const { sqlStatements: _dropped, ...rest } = change;
|
|
160
|
+
return {
|
|
161
|
+
...rest,
|
|
162
|
+
sql: coveredSql(change.name, primary.name)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return change;
|
|
166
|
+
});
|
|
167
|
+
const rebuildPositions = new Set(pending.map((change) => changes.indexOf(change)));
|
|
168
|
+
return [...rebuilt.filter((_, index) => rebuildPositions.has(index)), ...rebuilt.filter((_, index) => !rebuildPositions.has(index))];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Read everything the rebuild needs from the live database.
|
|
172
|
+
*
|
|
173
|
+
* Every query is SQLite-only (`sqlite_master`, `PRAGMA`), and the caller only
|
|
174
|
+
* reaches this for an engine already detected as SQLite.
|
|
175
|
+
*/
|
|
176
|
+
async function readSqliteRebuildContext(db, tableName) {
|
|
177
|
+
const createTableSql = readString((await db.query(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`, tableName)).rows[0]?.sql);
|
|
178
|
+
const columns = (await db.query(`PRAGMA table_info(${quoteIdentifier(tableName)})`)).rows.map((row) => readString(row.name)).filter((name) => Boolean(name));
|
|
179
|
+
const objectRows = await db.query(`SELECT name, sql FROM sqlite_master
|
|
180
|
+
WHERE tbl_name = ? AND sql IS NOT NULL
|
|
181
|
+
AND type IN ('index', 'trigger') AND name NOT LIKE 'sqlite_%'`, tableName);
|
|
182
|
+
const objects = [];
|
|
183
|
+
for (const row of objectRows.rows) {
|
|
184
|
+
const name = readString(row.name);
|
|
185
|
+
const sql = readString(row.sql);
|
|
186
|
+
if (name && sql) objects.push({
|
|
187
|
+
name,
|
|
188
|
+
sql
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const foreignKeysEnabled = readPragmaFlag(await db.query("PRAGMA foreign_keys"));
|
|
192
|
+
return {
|
|
193
|
+
createTableSql,
|
|
194
|
+
columns,
|
|
195
|
+
objects,
|
|
196
|
+
foreignKeysEnabled,
|
|
197
|
+
legacyAlterTable: readPragmaFlag(await db.query("PRAGMA legacy_alter_table")),
|
|
198
|
+
inboundReferences: foreignKeysEnabled ? await readInboundForeignKeys(db, tableName) : []
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Names of tables that declare a foreign key onto `tableName`.
|
|
203
|
+
*
|
|
204
|
+
* The table itself counts. A self-reference is copied verbatim into the
|
|
205
|
+
* staging table, which therefore becomes a real child of the live table, and
|
|
206
|
+
* `DROP TABLE` then cascades the *staged* rows away — verified: a two-row
|
|
207
|
+
* self-referencing table finishes the rebuild holding one row.
|
|
208
|
+
*/
|
|
209
|
+
async function readInboundForeignKeys(db, tableName) {
|
|
210
|
+
const tables = await db.query(`SELECT name FROM sqlite_master
|
|
211
|
+
WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`);
|
|
212
|
+
const referencing = [];
|
|
213
|
+
const target = tableName.toLowerCase();
|
|
214
|
+
for (const row of tables.rows) {
|
|
215
|
+
const name = readString(row.name);
|
|
216
|
+
if (!name) continue;
|
|
217
|
+
if ((await db.query(`PRAGMA foreign_key_list(${quoteIdentifier(name)})`)).rows.some((fk) => readString(fk.table).toLowerCase() === target)) referencing.push(name);
|
|
218
|
+
}
|
|
219
|
+
return referencing;
|
|
220
|
+
}
|
|
221
|
+
function readString(value) {
|
|
222
|
+
return typeof value === "string" ? value : "";
|
|
223
|
+
}
|
|
224
|
+
/** Read a single-row/single-column PRAGMA result as a boolean. */
|
|
225
|
+
function readPragmaFlag(result) {
|
|
226
|
+
const row = result.rows[0];
|
|
227
|
+
if (!row) return false;
|
|
228
|
+
const value = Object.values(row)[0];
|
|
229
|
+
if (typeof value === "number") return value !== 0;
|
|
230
|
+
if (typeof value === "boolean") return value;
|
|
231
|
+
if (typeof value === "string") return value !== "0" && value !== "";
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Build the ordered statement list for one table rebuild.
|
|
236
|
+
*
|
|
237
|
+
* Returns `null` when the live `CREATE TABLE` text cannot be rewritten (an
|
|
238
|
+
* unparsable statement, or a requested column that is not in it) — the caller
|
|
239
|
+
* then leaves the change as manual drift rather than emitting SQL that would
|
|
240
|
+
* "succeed" without fixing the column, which would re-diff forever.
|
|
241
|
+
*
|
|
242
|
+
* `PRAGMA legacy_alter_table` brackets the rename because SQLite ≥ 3.25
|
|
243
|
+
* re-parses the whole schema on `ALTER TABLE ... RENAME`: a view left
|
|
244
|
+
* referencing the just-dropped table makes the rename fail outright
|
|
245
|
+
* ("error in view … no such table"). Legacy mode renames the table without
|
|
246
|
+
* that reference rewriting, which is exactly what a rebuild wants — the name
|
|
247
|
+
* the views refer to is about to exist again. It is restored to its previous
|
|
248
|
+
* value immediately afterwards; a rolled-back batch leaves it set, which is
|
|
249
|
+
* inert for SMRT (nothing else in the toolchain renames a table) but is
|
|
250
|
+
* documented in `packages/core/agents/schema-paths.md`.
|
|
251
|
+
*/
|
|
252
|
+
function buildSqliteRebuildStatements(input) {
|
|
253
|
+
const { tableName, createTableSql, columnTypes, columns, objects, legacyAlterTable } = input;
|
|
254
|
+
const stagingTable = `${SQLITE_REBUILD_TABLE_PREFIX}${tableName}`;
|
|
255
|
+
const createStaging = rewriteSqliteCreateTable(createTableSql, {
|
|
256
|
+
tableName: stagingTable,
|
|
257
|
+
columnTypes
|
|
258
|
+
});
|
|
259
|
+
if (!createStaging) return null;
|
|
260
|
+
const quotedTable = quoteIdentifier(tableName);
|
|
261
|
+
const quotedStaging = quoteIdentifier(stagingTable);
|
|
262
|
+
const columnList = columns.map((name) => quoteIdentifier(name)).join(", ");
|
|
263
|
+
return {
|
|
264
|
+
statements: [
|
|
265
|
+
"PRAGMA defer_foreign_keys = ON",
|
|
266
|
+
`DROP TABLE IF EXISTS ${quotedStaging}`,
|
|
267
|
+
createStaging,
|
|
268
|
+
`INSERT INTO ${quotedStaging} (${columnList}) SELECT ${columnList} FROM ${quotedTable}`,
|
|
269
|
+
`DROP TABLE ${quotedTable}`,
|
|
270
|
+
"PRAGMA legacy_alter_table = ON",
|
|
271
|
+
`ALTER TABLE ${quotedStaging} RENAME TO ${quotedTable}`,
|
|
272
|
+
`PRAGMA legacy_alter_table = ${legacyAlterTable ? "ON" : "OFF"}`,
|
|
273
|
+
...objects.map((object) => object.sql)
|
|
274
|
+
],
|
|
275
|
+
copiedColumns: [...columns],
|
|
276
|
+
recreatedObjects: objects.map((object) => object.name)
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Rewrite a live `CREATE TABLE` statement into the rebuild's target shape:
|
|
281
|
+
* the same body under a new table name, with the declared type of each column
|
|
282
|
+
* in `columnTypes` replaced.
|
|
283
|
+
*
|
|
284
|
+
* Everything else is preserved verbatim — other columns, their defaults,
|
|
285
|
+
* `NOT NULL`/`PRIMARY KEY`/`CHECK`/`COLLATE` clauses, table-level
|
|
286
|
+
* constraints, and trailing table options (`WITHOUT ROWID`, `STRICT`).
|
|
287
|
+
*
|
|
288
|
+
* Returns `null` if the statement cannot be parsed or if any requested column
|
|
289
|
+
* is not present in it.
|
|
290
|
+
*/
|
|
291
|
+
function rewriteSqliteCreateTable(createTableSql, target) {
|
|
292
|
+
const parsed = parseCreateTableBody(createTableSql);
|
|
293
|
+
if (!parsed) return null;
|
|
294
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
295
|
+
for (const [name, type] of target.columnTypes) wanted.set(name.toLowerCase(), type);
|
|
296
|
+
const rewritten = [];
|
|
297
|
+
for (const item of parsed.items) {
|
|
298
|
+
const columnName = readColumnDefinitionName(item);
|
|
299
|
+
const newType = columnName ? wanted.get(columnName.toLowerCase()) : null;
|
|
300
|
+
if (!columnName || !newType) {
|
|
301
|
+
rewritten.push(item);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const replaced = replaceColumnType(item, newType);
|
|
305
|
+
if (!replaced) return null;
|
|
306
|
+
rewritten.push(replaced);
|
|
307
|
+
wanted.delete(columnName.toLowerCase());
|
|
308
|
+
}
|
|
309
|
+
if (wanted.size > 0) return null;
|
|
310
|
+
const tail = parsed.tail ? ` ${parsed.tail}` : "";
|
|
311
|
+
return `CREATE TABLE ${quoteIdentifier(target.tableName)} (\n ${rewritten.join(",\n ")}\n)${tail}`;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Split a `CREATE TABLE` statement into its top-level definition items plus
|
|
315
|
+
* any trailing table options, honoring SQLite's quoting (`'…'`, `"…"`,
|
|
316
|
+
* `` `…` ``, `[…]`) and comments so a quoted comma or paren cannot split the
|
|
317
|
+
* body.
|
|
318
|
+
*
|
|
319
|
+
* @internal exported for testing
|
|
320
|
+
*/
|
|
321
|
+
function parseCreateTableBody(sql) {
|
|
322
|
+
let index = 0;
|
|
323
|
+
let open = -1;
|
|
324
|
+
while (index < sql.length) {
|
|
325
|
+
const skipped = skipQuotedOrComment(sql, index);
|
|
326
|
+
if (skipped !== null) {
|
|
327
|
+
index = skipped;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (sql[index] === "(") {
|
|
331
|
+
open = index;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
index++;
|
|
335
|
+
}
|
|
336
|
+
if (open === -1) return null;
|
|
337
|
+
const items = [];
|
|
338
|
+
let depth = 1;
|
|
339
|
+
let itemStart = open + 1;
|
|
340
|
+
let close = -1;
|
|
341
|
+
index = open + 1;
|
|
342
|
+
while (index < sql.length) {
|
|
343
|
+
const skipped = skipQuotedOrComment(sql, index);
|
|
344
|
+
if (skipped !== null) {
|
|
345
|
+
index = skipped;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const char = sql[index];
|
|
349
|
+
if (char === "(") depth++;
|
|
350
|
+
else if (char === ")") {
|
|
351
|
+
depth--;
|
|
352
|
+
if (depth === 0) {
|
|
353
|
+
close = index;
|
|
354
|
+
items.push(sql.slice(itemStart, index));
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
} else if (char === "," && depth === 1) {
|
|
358
|
+
items.push(sql.slice(itemStart, index));
|
|
359
|
+
itemStart = index + 1;
|
|
360
|
+
}
|
|
361
|
+
index++;
|
|
362
|
+
}
|
|
363
|
+
if (close === -1) return null;
|
|
364
|
+
const trimmed = items.map((item) => item.trim()).filter(Boolean);
|
|
365
|
+
if (trimmed.length === 0) return null;
|
|
366
|
+
return {
|
|
367
|
+
items: trimmed,
|
|
368
|
+
tail: sql.slice(close + 1).trim().replace(/;+$/, "").trim()
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* If `sql[index]` opens a quoted identifier, string literal, or comment,
|
|
373
|
+
* return the index just past its end. Otherwise return `null`.
|
|
374
|
+
*/
|
|
375
|
+
function skipQuotedOrComment(sql, index) {
|
|
376
|
+
const char = sql[index];
|
|
377
|
+
if (char === "'" || char === "\"" || char === "`") {
|
|
378
|
+
let cursor = index + 1;
|
|
379
|
+
while (cursor < sql.length) {
|
|
380
|
+
if (sql[cursor] === char) {
|
|
381
|
+
if (sql[cursor + 1] === char) {
|
|
382
|
+
cursor += 2;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
return cursor + 1;
|
|
386
|
+
}
|
|
387
|
+
cursor++;
|
|
388
|
+
}
|
|
389
|
+
return sql.length;
|
|
390
|
+
}
|
|
391
|
+
if (char === "[") {
|
|
392
|
+
const end = sql.indexOf("]", index + 1);
|
|
393
|
+
return end === -1 ? sql.length : end + 1;
|
|
394
|
+
}
|
|
395
|
+
if (char === "-" && sql[index + 1] === "-") {
|
|
396
|
+
const end = sql.indexOf("\n", index);
|
|
397
|
+
return end === -1 ? sql.length : end + 1;
|
|
398
|
+
}
|
|
399
|
+
if (char === "/" && sql[index + 1] === "*") {
|
|
400
|
+
const end = sql.indexOf("*/", index + 2);
|
|
401
|
+
return end === -1 ? sql.length : end + 2;
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Keywords that start a *table*-level constraint, i.e. a body item that is not
|
|
407
|
+
* a column definition.
|
|
408
|
+
*/
|
|
409
|
+
var TABLE_CONSTRAINT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
410
|
+
"constraint",
|
|
411
|
+
"primary",
|
|
412
|
+
"unique",
|
|
413
|
+
"check",
|
|
414
|
+
"foreign"
|
|
415
|
+
]);
|
|
416
|
+
/**
|
|
417
|
+
* Keywords that end a column's type name and start its constraint list.
|
|
418
|
+
*/
|
|
419
|
+
var COLUMN_CONSTRAINT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
420
|
+
"constraint",
|
|
421
|
+
"primary",
|
|
422
|
+
"not",
|
|
423
|
+
"null",
|
|
424
|
+
"unique",
|
|
425
|
+
"check",
|
|
426
|
+
"default",
|
|
427
|
+
"collate",
|
|
428
|
+
"references",
|
|
429
|
+
"generated",
|
|
430
|
+
"as"
|
|
431
|
+
]);
|
|
432
|
+
/** Read the leading identifier of a body item (quoted or bare). */
|
|
433
|
+
function readLeadingIdentifier(item) {
|
|
434
|
+
let index = 0;
|
|
435
|
+
while (index < item.length && /\s/.test(item[index])) index++;
|
|
436
|
+
if (index >= item.length) return null;
|
|
437
|
+
const char = item[index];
|
|
438
|
+
if (char === "\"" || char === "`" || char === "[") {
|
|
439
|
+
const end = skipQuotedOrComment(item, index);
|
|
440
|
+
if (end === null) return null;
|
|
441
|
+
const raw = item.slice(index + 1, end - 1);
|
|
442
|
+
return {
|
|
443
|
+
name: char === "[" ? raw : raw.split(char + char).join(char),
|
|
444
|
+
end
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
const match = /^[A-Za-z_][\w$]*/.exec(item.slice(index));
|
|
448
|
+
if (!match) return null;
|
|
449
|
+
return {
|
|
450
|
+
name: match[0],
|
|
451
|
+
end: index + match[0].length
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* The column name of a body item, or `null` when the item is a table-level
|
|
456
|
+
* constraint rather than a column definition.
|
|
457
|
+
*
|
|
458
|
+
* @internal exported for testing
|
|
459
|
+
*/
|
|
460
|
+
function readColumnDefinitionName(item) {
|
|
461
|
+
const identifier = readLeadingIdentifier(item);
|
|
462
|
+
if (!identifier) return null;
|
|
463
|
+
if (!/^\s*["`[]/.test(item) && TABLE_CONSTRAINT_KEYWORDS.has(identifier.name.toLowerCase())) return null;
|
|
464
|
+
return identifier.name;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Replace the declared type of a column definition, keeping its name and every
|
|
468
|
+
* constraint that follows. A column declared without a type gets one inserted.
|
|
469
|
+
*
|
|
470
|
+
* @internal exported for testing
|
|
471
|
+
*/
|
|
472
|
+
function replaceColumnType(item, newType) {
|
|
473
|
+
const identifier = readLeadingIdentifier(item);
|
|
474
|
+
if (!identifier) return null;
|
|
475
|
+
const head = item.slice(0, identifier.end);
|
|
476
|
+
let index = identifier.end;
|
|
477
|
+
let typeEnd = index;
|
|
478
|
+
while (index < item.length) {
|
|
479
|
+
while (index < item.length && /\s/.test(item[index])) index++;
|
|
480
|
+
const match = /^[A-Za-z_][\w$]*/.exec(item.slice(index));
|
|
481
|
+
if (!match || COLUMN_CONSTRAINT_KEYWORDS.has(match[0].toLowerCase())) break;
|
|
482
|
+
index += match[0].length;
|
|
483
|
+
typeEnd = index;
|
|
484
|
+
}
|
|
485
|
+
if (typeEnd > identifier.end) {
|
|
486
|
+
let cursor = typeEnd;
|
|
487
|
+
while (cursor < item.length && /\s/.test(item[cursor])) cursor++;
|
|
488
|
+
if (item[cursor] === "(") {
|
|
489
|
+
let depth = 0;
|
|
490
|
+
while (cursor < item.length) {
|
|
491
|
+
const skipped = skipQuotedOrComment(item, cursor);
|
|
492
|
+
if (skipped !== null) {
|
|
493
|
+
cursor = skipped;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (item[cursor] === "(") depth++;
|
|
497
|
+
else if (item[cursor] === ")") {
|
|
498
|
+
depth--;
|
|
499
|
+
if (depth === 0) {
|
|
500
|
+
typeEnd = cursor + 1;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
cursor++;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const rest = item.slice(typeEnd);
|
|
509
|
+
return `${head} ${newType}${rest.length === 0 || /^\s/.test(rest) ? "" : " "}${rest}`;
|
|
510
|
+
}
|
|
511
|
+
//#endregion
|
|
512
|
+
export { SQLITE_REBUILD_TABLE_PREFIX, buildSqliteRebuildStatements, isSqliteRebuildPlaceholder, parseCreateTableBody, planSqliteTableRebuilds, readColumnDefinitionName, replaceColumnType, rewriteSqliteCreateTable, sqliteRebuildPlaceholderSql };
|
|
513
|
+
|
|
514
|
+
//# sourceMappingURL=sqlite-rebuild.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-rebuild.js","names":[],"sources":["../../src/migrations/sqlite-rebuild.ts"],"sourcesContent":["/**\n * SQLite table-rebuild migrations (issue #2370)\n *\n * SQLite has no `ALTER TABLE ... ALTER COLUMN ... TYPE`. Before this module\n * every type-bucket change on SQLite (the canonical one being a numeric\n * default edited from `0` to `0.0`, i.e. INTEGER → REAL) turned into an\n * advisory differ comment, which `db:migrate` classifies as\n * *manual intervention* and exits 1 for — forever, because nothing in the\n * toolchain could ever perform the change.\n *\n * This module turns that comment into the executable rebuild SQLite's own\n * documentation prescribes (\"Making Other Kinds Of Table Schema Changes\",\n * lang_altertable.html):\n *\n * 1. `PRAGMA defer_foreign_keys = ON` — defers foreign-key enforcement to\n * COMMIT so the window where the table does not exist cannot raise. It is\n * honored *inside* a transaction (unlike `PRAGMA foreign_keys`, which is a\n * silent no-op there) and resets itself at COMMIT/ROLLBACK.\n * 2. Create a new table under a reserved `_smrt_rebuild_` name carrying the\n * target shape.\n * 3. Copy every row across.\n * 4. Drop the original table.\n * 5. Rename the new table into its place, with `PRAGMA legacy_alter_table`\n * ON for the rename itself (see {@link buildSqliteRebuildStatements}).\n * 6. Recreate the indexes and triggers that died with the original table.\n *\n * The whole plan is a plain statement list, so it is executed by the\n * `MigrationTracker` inside the same transaction as the rest of the batch:\n * a failure anywhere rolls the table back to its original shape and contents.\n *\n * ## The target shape comes from the live DDL, not from the manifest\n *\n * The new table is the *original* `CREATE TABLE` text with only the drifted\n * columns' type tokens replaced. That deliberately preserves everything the\n * manifest does not model or does not own here: columns the manifest has\n * dropped (SMRT is additive — a rebuild must not become an implicit\n * `DROP COLUMN`), table-level constraints, `CHECK`s, collations, and table\n * options such as `WITHOUT ROWID`. The `add_column` changes in the same batch\n * still run their own `ALTER TABLE ... ADD COLUMN`, against the rebuilt table\n * — {@link planSqliteTableRebuilds} hoists the rebuild ahead of them for\n * exactly that reason.\n *\n * ## Why the copy carries no CAST\n *\n * The copy is a plain `INSERT INTO new SELECT ... FROM old`; SQLite applies\n * the destination column's *affinity* on insert, which is exactly the\n * conversion a fresh table would have performed (integer `1` becomes real\n * `1.0` in a REAL column). An explicit `CAST` would be strictly worse: casting\n * a non-numeric TEXT value to REAL/INTEGER silently yields `0`, and casting an\n * ISO timestamp string to SQLite's NUMERIC-affinity `DATETIME` silently yields\n * the leading year. Affinity conversion leaves values it cannot convert\n * untouched instead of destroying them.\n *\n * ## When the rebuild refuses\n *\n * With `PRAGMA foreign_keys` enabled — which the SMRT SQLite adapter is by\n * default — `DROP TABLE` performs an implicit `DELETE FROM` that fires\n * `ON DELETE` actions on children. A child with `ON DELETE CASCADE` therefore\n * loses its rows, and `defer_foreign_keys` does not help: it defers\n * constraint *checks*, not FK *actions*. So when any table declares a foreign\n * key onto the rebuild target and enforcement is on, the plan is refused and\n * the change stays a manual intervention. That includes the target's own\n * self-references, because the staging table copies them and so becomes a\n * child of the table being dropped.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { quoteIdentifier } from '../schema/sql-identifiers.js';\nimport type { SchemaChange, SQLDataType } from '../schema/types.js';\nimport type { DatabaseInterface } from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Prefix of the reserved staging table a rebuild creates. `_smrt_`-prefixed\n * names are SMRT system tables, which the differ's dropped-table sweep skips,\n * so a staging table left behind by a non-transactional adapter cannot be\n * mistaken for user data.\n */\nexport const SQLITE_REBUILD_TABLE_PREFIX = '_smrt_rebuild_';\n\n/**\n * The advisory SQL the differ emits for a SQLite type upgrade it cannot\n * perform with an `ALTER TABLE`. `planSqliteTableRebuilds` replaces it with an\n * executable plan; when it cannot, the comment survives and `db:migrate`\n * reports the column as manual drift exactly as it did before #2370.\n */\nexport function sqliteRebuildPlaceholderSql(columnName: string): string {\n return `-- SQLite: Type upgrade for ${quoteIdentifier(columnName)} requires table recreation`;\n}\n\n/**\n * True when `sql` is the differ's \"requires table recreation\" placeholder —\n * i.e. a type upgrade waiting for this module to turn it into a rebuild.\n */\nexport function isSqliteRebuildPlaceholder(sql: string | undefined): boolean {\n return typeof sql === 'string' && sql.includes('requires table recreation');\n}\n\nfunction blockedSql(columnName: string, reason: string): string {\n // Must stay a single line, and must not read as a no-op: the CLI classifies\n // any comment that says \"no change needed\" as noop and drops it silently.\n return `-- SQLite: ${quoteIdentifier(columnName)} requires a table rebuild, blocked: ${reason}`;\n}\n\nfunction coveredSql(columnName: string, rebuiltWith: string): string {\n // Classified `noop` by the CLI (`no change needed`): the sibling change\n // carrying the plan rebuilds the whole table, this column included.\n return `-- SQLite: ${quoteIdentifier(columnName)} is rebuilt with ${quoteIdentifier(rebuiltWith)} in one table rebuild (no change needed)`;\n}\n\n/** A rebuild plan: the ordered statements plus what they touch. */\nexport interface SqliteTableRebuildPlan {\n /** Ordered, executable statements. Run them in one transaction. */\n statements: string[];\n /** Columns copied from the old table into the new one, in table order. */\n copiedColumns: string[];\n /** Names of the indexes and triggers recreated after the swap. */\n recreatedObjects: string[];\n}\n\n/** Live SQLite state a rebuild needs before it can plan anything. */\ninterface SqliteRebuildContext {\n /** `sqlite_master.sql` for the table (SQLite normalizes IF NOT EXISTS away). */\n createTableSql: string;\n /**\n * Column names in `PRAGMA table_info` order. That pragma omits generated\n * columns, which is what the copy wants: they are not insertable and the\n * preserved DDL recomputes them in the new table.\n */\n columns: string[];\n /** Indexes and triggers that will die with the dropped table. */\n objects: { name: string; sql: string }[];\n /** Whether `PRAGMA foreign_keys` enforcement is on for this connection. */\n foreignKeysEnabled: boolean;\n /** Current `PRAGMA legacy_alter_table` value, restored after the rename. */\n legacyAlterTable: boolean;\n /** Other tables declaring a foreign key onto this table. */\n inboundReferences: string[];\n}\n\nexport interface PlanSqliteTableRebuildsOptions {\n db: DatabaseInterface;\n tableName: string;\n /** The table's changes, as produced by the differ's column comparison. */\n changes: SchemaChange[];\n /** Abstract → SQLite type mapping (the engine's DDL strategy `mapType`). */\n mapType: (type: SQLDataType) => string;\n}\n\n/**\n * Replace every \"requires table recreation\" placeholder for `tableName` with\n * one executable rebuild.\n *\n * All of the table's drifted columns are retyped by a *single* rebuild, so the\n * first placeholder carries the plan and the rest become no-ops that reference\n * it. Returns the changes unchanged when there is nothing to rebuild, and\n * leaves the placeholders in place (annotated with the reason) when the\n * rebuild cannot be planned safely — the pre-#2370 manual-intervention\n * behavior.\n *\n * When a plan is produced, the rebuild is hoisted to the front of the table's\n * changes. The plan's staging DDL and copy list are captured from the live\n * schema *at diff time*, so a same-batch `ALTER TABLE ... ADD COLUMN` that ran\n * first would be silently undone by the rebuild — both statements succeed, the\n * transaction commits, and the new column is simply gone. The differ emits\n * changes in manifest field order, which puts an `add_column` first whenever\n * the new field is declared above the retyped one, so the order has to be\n * imposed here rather than assumed. Rebuild first, then add columns to the\n * rebuilt table.\n */\nexport async function planSqliteTableRebuilds(\n options: PlanSqliteTableRebuildsOptions,\n): Promise<SchemaChange[]> {\n const { db, tableName, changes, mapType } = options;\n\n const pending = changes.filter(\n (change) =>\n change.type === 'type_upgrade' &&\n change.name &&\n change.column &&\n isSqliteRebuildPlaceholder(change.sql),\n );\n if (pending.length === 0) {\n return changes;\n }\n\n const block = (reason: string): SchemaChange[] => {\n logger.debug(\n `[SQLiteRebuild] Cannot rebuild ${tableName}: ${reason}; leaving ${pending.length} type upgrade(s) as manual drift`,\n );\n return changes.map((change) =>\n pending.includes(change)\n ? { ...change, sql: blockedSql(change.name as string, reason) }\n : change,\n );\n };\n\n let context: SqliteRebuildContext;\n try {\n context = await readSqliteRebuildContext(db, tableName);\n } catch (error) {\n return block(\n `live schema introspection failed (${error instanceof Error ? error.message : String(error)})`,\n );\n }\n\n if (!context.createTableSql) {\n return block('no CREATE TABLE statement found in sqlite_master');\n }\n if (context.columns.length === 0) {\n return block('PRAGMA table_info returned no columns');\n }\n if (context.foreignKeysEnabled && context.inboundReferences.length > 0) {\n return block(\n `PRAGMA foreign_keys is ON and ${context.inboundReferences\n .map((name) => `\"${name}\"`)\n .join(\n ', ',\n )} declare a foreign key onto this table, so DROP TABLE would fire their ON DELETE actions`,\n );\n }\n\n const columnTypes = new Map<string, string>();\n for (const change of pending) {\n columnTypes.set(\n change.name as string,\n mapType((change.column as { type: SQLDataType }).type),\n );\n }\n\n const plan = buildSqliteRebuildStatements({\n tableName,\n createTableSql: context.createTableSql,\n columnTypes,\n columns: context.columns,\n objects: context.objects,\n legacyAlterTable: context.legacyAlterTable,\n });\n\n if (!plan) {\n return block(\n 'the live CREATE TABLE statement could not be rewritten for the target column types',\n );\n }\n\n const [primary, ...covered] = pending;\n const rebuilt = changes.map((change) => {\n if (change === primary) {\n return {\n ...change,\n // `sql` stays a real statement so the CLI classifies this upgrade as\n // executable; every consumer that actually runs the change prefers\n // `sqlStatements`.\n sql: plan.statements[0],\n sqlStatements: plan.statements,\n };\n }\n if (covered.includes(change)) {\n const { sqlStatements: _dropped, ...rest } = change;\n return {\n ...rest,\n sql: coveredSql(change.name as string, primary.name as string),\n };\n }\n return change;\n });\n\n // Hoist the rebuild ahead of the table's other column changes, keeping both\n // groups in their original relative order. Consumers execute `diff.changes`\n // in order, and the plan was built from the pre-batch table shape.\n const rebuildPositions = new Set(\n pending.map((change) => changes.indexOf(change)),\n );\n return [\n ...rebuilt.filter((_, index) => rebuildPositions.has(index)),\n ...rebuilt.filter((_, index) => !rebuildPositions.has(index)),\n ];\n}\n\n/**\n * Read everything the rebuild needs from the live database.\n *\n * Every query is SQLite-only (`sqlite_master`, `PRAGMA`), and the caller only\n * reaches this for an engine already detected as SQLite.\n */\nasync function readSqliteRebuildContext(\n db: DatabaseInterface,\n tableName: string,\n): Promise<SqliteRebuildContext> {\n const tableRows = await db.query(\n `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`,\n tableName,\n );\n const createTableSql = readString(\n (tableRows.rows[0] as { sql?: unknown } | undefined)?.sql,\n );\n\n const columnRows = await db.query(\n `PRAGMA table_info(${quoteIdentifier(tableName)})`,\n );\n const columns = columnRows.rows\n .map((row) => readString((row as { name?: unknown }).name))\n .filter((name): name is string => Boolean(name));\n\n // `sql IS NULL` marks the implicit indexes SQLite creates for inline\n // PRIMARY KEY / UNIQUE constraints; those are carried by the CREATE TABLE\n // text and must not be replayed separately.\n const objectRows = await db.query(\n `SELECT name, sql FROM sqlite_master\n WHERE tbl_name = ? AND sql IS NOT NULL\n AND type IN ('index', 'trigger') AND name NOT LIKE 'sqlite_%'`,\n tableName,\n );\n const objects: { name: string; sql: string }[] = [];\n for (const row of objectRows.rows as { name?: unknown; sql?: unknown }[]) {\n const name = readString(row.name);\n const sql = readString(row.sql);\n if (name && sql) {\n objects.push({ name, sql });\n }\n }\n\n const foreignKeysEnabled = readPragmaFlag(\n await db.query('PRAGMA foreign_keys'),\n );\n const legacyAlterTable = readPragmaFlag(\n await db.query('PRAGMA legacy_alter_table'),\n );\n\n const inboundReferences = foreignKeysEnabled\n ? await readInboundForeignKeys(db, tableName)\n : [];\n\n return {\n createTableSql,\n columns,\n objects,\n foreignKeysEnabled,\n legacyAlterTable,\n inboundReferences,\n };\n}\n\n/**\n * Names of tables that declare a foreign key onto `tableName`.\n *\n * The table itself counts. A self-reference is copied verbatim into the\n * staging table, which therefore becomes a real child of the live table, and\n * `DROP TABLE` then cascades the *staged* rows away — verified: a two-row\n * self-referencing table finishes the rebuild holding one row.\n */\nasync function readInboundForeignKeys(\n db: DatabaseInterface,\n tableName: string,\n): Promise<string[]> {\n const tables = await db.query(\n `SELECT name FROM sqlite_master\n WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`,\n );\n\n const referencing: string[] = [];\n const target = tableName.toLowerCase();\n for (const row of tables.rows as { name?: unknown }[]) {\n const name = readString(row.name);\n if (!name) continue;\n const fks = await db.query(\n `PRAGMA foreign_key_list(${quoteIdentifier(name)})`,\n );\n const referencesTarget = (fks.rows as { table?: unknown }[]).some(\n (fk) => readString(fk.table).toLowerCase() === target,\n );\n if (referencesTarget) {\n referencing.push(name);\n }\n }\n\n return referencing;\n}\n\nfunction readString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\n/** Read a single-row/single-column PRAGMA result as a boolean. */\nfunction readPragmaFlag(result: { rows: Record<string, unknown>[] }): boolean {\n const row = result.rows[0];\n if (!row) return false;\n const value = Object.values(row)[0];\n if (typeof value === 'number') return value !== 0;\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') return value !== '0' && value !== '';\n return false;\n}\n\nexport interface BuildSqliteRebuildStatementsInput {\n tableName: string;\n /** The live `sqlite_master.sql` CREATE TABLE text. */\n createTableSql: string;\n /** Column name → target SQLite type, for the columns being retyped. */\n columnTypes: Map<string, string>;\n /** Every column of the live table, in `PRAGMA table_info` order. */\n columns: string[];\n /** Indexes and triggers to replay after the swap. */\n objects: { name: string; sql: string }[];\n /** Current `PRAGMA legacy_alter_table` value, restored after the rename. */\n legacyAlterTable: boolean;\n}\n\n/**\n * Build the ordered statement list for one table rebuild.\n *\n * Returns `null` when the live `CREATE TABLE` text cannot be rewritten (an\n * unparsable statement, or a requested column that is not in it) — the caller\n * then leaves the change as manual drift rather than emitting SQL that would\n * \"succeed\" without fixing the column, which would re-diff forever.\n *\n * `PRAGMA legacy_alter_table` brackets the rename because SQLite ≥ 3.25\n * re-parses the whole schema on `ALTER TABLE ... RENAME`: a view left\n * referencing the just-dropped table makes the rename fail outright\n * (\"error in view … no such table\"). Legacy mode renames the table without\n * that reference rewriting, which is exactly what a rebuild wants — the name\n * the views refer to is about to exist again. It is restored to its previous\n * value immediately afterwards; a rolled-back batch leaves it set, which is\n * inert for SMRT (nothing else in the toolchain renames a table) but is\n * documented in `packages/core/agents/schema-paths.md`.\n */\nexport function buildSqliteRebuildStatements(\n input: BuildSqliteRebuildStatementsInput,\n): SqliteTableRebuildPlan | null {\n const {\n tableName,\n createTableSql,\n columnTypes,\n columns,\n objects,\n legacyAlterTable,\n } = input;\n\n const stagingTable = `${SQLITE_REBUILD_TABLE_PREFIX}${tableName}`;\n const createStaging = rewriteSqliteCreateTable(createTableSql, {\n tableName: stagingTable,\n columnTypes,\n });\n if (!createStaging) {\n return null;\n }\n\n const quotedTable = quoteIdentifier(tableName);\n const quotedStaging = quoteIdentifier(stagingTable);\n const columnList = columns.map((name) => quoteIdentifier(name)).join(', ');\n\n const statements = [\n // Deferred FK enforcement survives the window where the table is absent.\n // Unlike `PRAGMA foreign_keys`, this one is honored inside a transaction\n // and resets itself at COMMIT/ROLLBACK.\n 'PRAGMA defer_foreign_keys = ON',\n // Idempotent for a retry after a partially applied non-transactional run.\n `DROP TABLE IF EXISTS ${quotedStaging}`,\n createStaging,\n `INSERT INTO ${quotedStaging} (${columnList}) SELECT ${columnList} FROM ${quotedTable}`,\n `DROP TABLE ${quotedTable}`,\n 'PRAGMA legacy_alter_table = ON',\n `ALTER TABLE ${quotedStaging} RENAME TO ${quotedTable}`,\n `PRAGMA legacy_alter_table = ${legacyAlterTable ? 'ON' : 'OFF'}`,\n // The indexes and triggers went down with the dropped table. Their\n // original text still names the table correctly (it was renamed back), so\n // replaying it verbatim preserves partial predicates, expression indexes,\n // and trigger bodies that a manifest-driven regeneration would flatten.\n ...objects.map((object) => object.sql),\n ];\n\n return {\n statements,\n copiedColumns: [...columns],\n recreatedObjects: objects.map((object) => object.name),\n };\n}\n\n/**\n * Rewrite a live `CREATE TABLE` statement into the rebuild's target shape:\n * the same body under a new table name, with the declared type of each column\n * in `columnTypes` replaced.\n *\n * Everything else is preserved verbatim — other columns, their defaults,\n * `NOT NULL`/`PRIMARY KEY`/`CHECK`/`COLLATE` clauses, table-level\n * constraints, and trailing table options (`WITHOUT ROWID`, `STRICT`).\n *\n * Returns `null` if the statement cannot be parsed or if any requested column\n * is not present in it.\n */\nexport function rewriteSqliteCreateTable(\n createTableSql: string,\n target: { tableName: string; columnTypes: Map<string, string> },\n): string | null {\n const parsed = parseCreateTableBody(createTableSql);\n if (!parsed) {\n return null;\n }\n\n const wanted = new Map<string, string>();\n for (const [name, type] of target.columnTypes) {\n wanted.set(name.toLowerCase(), type);\n }\n\n const rewritten: string[] = [];\n for (const item of parsed.items) {\n const columnName = readColumnDefinitionName(item);\n const newType = columnName ? wanted.get(columnName.toLowerCase()) : null;\n if (!columnName || !newType) {\n rewritten.push(item);\n continue;\n }\n const replaced = replaceColumnType(item, newType);\n if (!replaced) {\n return null;\n }\n rewritten.push(replaced);\n wanted.delete(columnName.toLowerCase());\n }\n\n if (wanted.size > 0) {\n // A column the differ wants retyped is not in the live DDL. Rebuilding\n // would not fix the drift, so refuse instead of emitting a no-op plan.\n return null;\n }\n\n const tail = parsed.tail ? ` ${parsed.tail}` : '';\n return `CREATE TABLE ${quoteIdentifier(target.tableName)} (\\n ${rewritten.join(\n ',\\n ',\n )}\\n)${tail}`;\n}\n\ninterface ParsedCreateTable {\n /** Top-level, comma-separated column and constraint definitions. */\n items: string[];\n /** Table options after the closing paren (`WITHOUT ROWID`, `STRICT`). */\n tail: string;\n}\n\n/**\n * Split a `CREATE TABLE` statement into its top-level definition items plus\n * any trailing table options, honoring SQLite's quoting (`'…'`, `\"…\"`,\n * `` `…` ``, `[…]`) and comments so a quoted comma or paren cannot split the\n * body.\n *\n * @internal exported for testing\n */\nexport function parseCreateTableBody(sql: string): ParsedCreateTable | null {\n let index = 0;\n let open = -1;\n while (index < sql.length) {\n const skipped = skipQuotedOrComment(sql, index);\n if (skipped !== null) {\n index = skipped;\n continue;\n }\n if (sql[index] === '(') {\n open = index;\n break;\n }\n index++;\n }\n if (open === -1) {\n return null;\n }\n\n const items: string[] = [];\n let depth = 1;\n let itemStart = open + 1;\n let close = -1;\n index = open + 1;\n while (index < sql.length) {\n const skipped = skipQuotedOrComment(sql, index);\n if (skipped !== null) {\n index = skipped;\n continue;\n }\n const char = sql[index];\n if (char === '(') {\n depth++;\n } else if (char === ')') {\n depth--;\n if (depth === 0) {\n close = index;\n items.push(sql.slice(itemStart, index));\n break;\n }\n } else if (char === ',' && depth === 1) {\n items.push(sql.slice(itemStart, index));\n itemStart = index + 1;\n }\n index++;\n }\n\n if (close === -1) {\n return null;\n }\n\n const trimmed = items.map((item) => item.trim()).filter(Boolean);\n if (trimmed.length === 0) {\n return null;\n }\n\n return {\n items: trimmed,\n tail: sql\n .slice(close + 1)\n .trim()\n .replace(/;+$/, '')\n .trim(),\n };\n}\n\n/**\n * If `sql[index]` opens a quoted identifier, string literal, or comment,\n * return the index just past its end. Otherwise return `null`.\n */\nfunction skipQuotedOrComment(sql: string, index: number): number | null {\n const char = sql[index];\n\n if (char === \"'\" || char === '\"' || char === '`') {\n let cursor = index + 1;\n while (cursor < sql.length) {\n if (sql[cursor] === char) {\n // A doubled delimiter is an escaped one, not the end of the token.\n if (sql[cursor + 1] === char) {\n cursor += 2;\n continue;\n }\n return cursor + 1;\n }\n cursor++;\n }\n return sql.length;\n }\n\n if (char === '[') {\n const end = sql.indexOf(']', index + 1);\n return end === -1 ? sql.length : end + 1;\n }\n\n if (char === '-' && sql[index + 1] === '-') {\n const end = sql.indexOf('\\n', index);\n return end === -1 ? sql.length : end + 1;\n }\n\n if (char === '/' && sql[index + 1] === '*') {\n const end = sql.indexOf('*/', index + 2);\n return end === -1 ? sql.length : end + 2;\n }\n\n return null;\n}\n\n/**\n * Keywords that start a *table*-level constraint, i.e. a body item that is not\n * a column definition.\n */\nconst TABLE_CONSTRAINT_KEYWORDS = new Set([\n 'constraint',\n 'primary',\n 'unique',\n 'check',\n 'foreign',\n]);\n\n/**\n * Keywords that end a column's type name and start its constraint list.\n */\nconst COLUMN_CONSTRAINT_KEYWORDS = new Set([\n 'constraint',\n 'primary',\n 'not',\n 'null',\n 'unique',\n 'check',\n 'default',\n 'collate',\n 'references',\n 'generated',\n 'as',\n]);\n\ninterface Identifier {\n name: string;\n end: number;\n}\n\n/** Read the leading identifier of a body item (quoted or bare). */\nfunction readLeadingIdentifier(item: string): Identifier | null {\n let index = 0;\n while (index < item.length && /\\s/.test(item[index])) index++;\n if (index >= item.length) return null;\n\n const char = item[index];\n if (char === '\"' || char === '`' || char === '[') {\n const end = skipQuotedOrComment(item, index);\n if (end === null) return null;\n const raw = item.slice(index + 1, end - 1);\n // `[…]` has no escape form; `\"…\"` / `` `…` `` double the delimiter.\n return {\n name: char === '[' ? raw : raw.split(char + char).join(char),\n end,\n };\n }\n\n const match = /^[A-Za-z_][\\w$]*/.exec(item.slice(index));\n if (!match) return null;\n return { name: match[0], end: index + match[0].length };\n}\n\n/**\n * The column name of a body item, or `null` when the item is a table-level\n * constraint rather than a column definition.\n *\n * @internal exported for testing\n */\nexport function readColumnDefinitionName(item: string): string | null {\n const identifier = readLeadingIdentifier(item);\n if (!identifier) return null;\n\n const isQuoted = /^\\s*[\"`[]/.test(item);\n if (\n !isQuoted &&\n TABLE_CONSTRAINT_KEYWORDS.has(identifier.name.toLowerCase())\n ) {\n return null;\n }\n return identifier.name;\n}\n\n/**\n * Replace the declared type of a column definition, keeping its name and every\n * constraint that follows. A column declared without a type gets one inserted.\n *\n * @internal exported for testing\n */\nexport function replaceColumnType(\n item: string,\n newType: string,\n): string | null {\n const identifier = readLeadingIdentifier(item);\n if (!identifier) return null;\n\n const head = item.slice(0, identifier.end);\n let index = identifier.end;\n\n // The type name is one or more bare words followed by an optional\n // `(size[, size])` — everything before the first column-constraint keyword.\n let typeEnd = index;\n while (index < item.length) {\n while (index < item.length && /\\s/.test(item[index])) index++;\n const match = /^[A-Za-z_][\\w$]*/.exec(item.slice(index));\n if (!match || COLUMN_CONSTRAINT_KEYWORDS.has(match[0].toLowerCase())) {\n break;\n }\n index += match[0].length;\n typeEnd = index;\n }\n\n // Optional parenthesized type argument, e.g. VARCHAR(255).\n if (typeEnd > identifier.end) {\n let cursor = typeEnd;\n while (cursor < item.length && /\\s/.test(item[cursor])) cursor++;\n if (item[cursor] === '(') {\n let depth = 0;\n while (cursor < item.length) {\n const skipped = skipQuotedOrComment(item, cursor);\n if (skipped !== null) {\n cursor = skipped;\n continue;\n }\n if (item[cursor] === '(') depth++;\n else if (item[cursor] === ')') {\n depth--;\n if (depth === 0) {\n typeEnd = cursor + 1;\n break;\n }\n }\n cursor++;\n }\n }\n }\n\n const rest = item.slice(typeEnd);\n const separator = rest.length === 0 || /^\\s/.test(rest) ? '' : ' ';\n return `${head} ${newType}${separator}${rest}`;\n}\n"],"mappings":"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"}
|