@prisma-next/target-sqlite 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract-free.d.mts +35 -2
- package/dist/contract-free.d.mts.map +1 -1
- package/dist/contract-free.mjs +3 -22
- package/dist/contract-free.mjs.map +1 -1
- package/dist/control.d.mts +5 -8
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +18 -20
- package/dist/control.mjs.map +1 -1
- package/dist/ddl-DrtjQMFK.mjs +68 -0
- package/dist/ddl-DrtjQMFK.mjs.map +1 -0
- package/dist/{descriptor-meta-Dxx2A6PT.mjs → descriptor-meta-DxmEeTJ-.mjs} +10 -3
- package/dist/descriptor-meta-DxmEeTJ-.mjs.map +1 -0
- package/dist/migration.d.mts +4 -46
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +4 -3
- package/dist/migration.mjs.map +1 -1
- package/dist/op-factory-call-DmdfD1yd.mjs +794 -0
- package/dist/op-factory-call-DmdfD1yd.mjs.map +1 -0
- package/dist/op-factory-call.d.mts +22 -12
- package/dist/op-factory-call.d.mts.map +1 -1
- package/dist/op-factory-call.mjs +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/{planner-DSNDwQy9.mjs → planner-Ciq8p_dL.mjs} +80 -12
- package/dist/planner-Ciq8p_dL.mjs.map +1 -0
- package/dist/{planner-produced-sqlite-migration-DowV_vHw.mjs → planner-produced-sqlite-migration-0xPEm3R1.mjs} +9 -5
- package/dist/planner-produced-sqlite-migration-0xPEm3R1.mjs.map +1 -0
- package/dist/{planner-produced-sqlite-migration-C1yqJAiM.d.mts → planner-produced-sqlite-migration-CpgsY-M9.d.mts} +5 -4
- package/dist/planner-produced-sqlite-migration-CpgsY-M9.d.mts.map +1 -0
- package/dist/planner-produced-sqlite-migration.d.mts +1 -1
- package/dist/planner-produced-sqlite-migration.mjs +1 -1
- package/dist/planner.d.mts +5 -2
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +1 -1
- package/dist/render-ops-BDW2tUeR.mjs +22 -0
- package/dist/render-ops-BDW2tUeR.mjs.map +1 -0
- package/dist/render-ops.d.mts +2 -1
- package/dist/render-ops.d.mts.map +1 -1
- package/dist/render-ops.mjs +1 -1
- package/dist/runtime.d.mts +1 -0
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +2 -2
- package/dist/shared-Dhc8mLK1.d.mts.map +1 -1
- package/dist/{sqlite-contract-serializer-jcRu8aHh.mjs → sqlite-contract-serializer--iaDgC8e.mjs} +17 -6
- package/dist/sqlite-contract-serializer--iaDgC8e.mjs.map +1 -0
- package/dist/sqlite-migration-A0rwqPOG.mjs +92 -0
- package/dist/sqlite-migration-A0rwqPOG.mjs.map +1 -0
- package/dist/sqlite-migration-DVfhQwN_.d.mts +75 -0
- package/dist/sqlite-migration-DVfhQwN_.d.mts.map +1 -0
- package/package.json +18 -18
- package/src/contract-free/checks.ts +75 -0
- package/src/core/control-target.ts +4 -4
- package/src/core/errors.ts +28 -0
- package/src/core/migrations/issue-planner.ts +151 -8
- package/src/core/migrations/op-factory-call.ts +332 -45
- package/src/core/migrations/operations/columns.ts +32 -26
- package/src/core/migrations/operations/indexes.ts +31 -27
- package/src/core/migrations/operations/shared.ts +11 -3
- package/src/core/migrations/operations/tables.ts +39 -37
- package/src/core/migrations/planner-ddl-builders.ts +7 -16
- package/src/core/migrations/planner-produced-sqlite-migration.ts +8 -2
- package/src/core/migrations/planner-strategies.ts +3 -3
- package/src/core/migrations/planner.ts +14 -2
- package/src/core/migrations/render-ops.ts +37 -9
- package/src/core/migrations/runner.ts +16 -12
- package/src/core/migrations/sqlite-migration.ts +119 -1
- package/src/core/sqlite-contract-serializer.ts +5 -0
- package/src/core/sqlite-unbound-database.ts +30 -54
- package/src/exports/contract-free.ts +8 -0
- package/src/exports/migration.ts +8 -3
- package/dist/descriptor-meta-Dxx2A6PT.mjs.map +0 -1
- package/dist/descriptor-meta-runtime-BkXK3OjD.mjs +0 -12
- package/dist/descriptor-meta-runtime-BkXK3OjD.mjs.map +0 -1
- package/dist/op-factory-call-DymqdXQW.mjs +0 -279
- package/dist/op-factory-call-DymqdXQW.mjs.map +0 -1
- package/dist/planner-DSNDwQy9.mjs.map +0 -1
- package/dist/planner-produced-sqlite-migration-C1yqJAiM.d.mts.map +0 -1
- package/dist/planner-produced-sqlite-migration-DowV_vHw.mjs.map +0 -1
- package/dist/render-ops-CFRbJ3Yb.mjs +0 -8
- package/dist/render-ops-CFRbJ3Yb.mjs.map +0 -1
- package/dist/sqlite-contract-serializer-jcRu8aHh.mjs.map +0 -1
- package/dist/sqlite-migration-CUqgmzQH.mjs +0 -16
- package/dist/sqlite-migration-CUqgmzQH.mjs.map +0 -1
- package/dist/sqlite-migration-D4XGYzgQ.d.mts +0 -17
- package/dist/sqlite-migration-D4XGYzgQ.d.mts.map +0 -1
- package/dist/tables-CjB7vXCr.mjs +0 -412
- package/dist/tables-CjB7vXCr.mjs.map +0 -1
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { n as stripOuterParens } from "./default-normalizer-DuoHj9-O.mjs";
|
|
2
|
+
import { i as tableExistsAst, n as columnExistsAst, r as indexExistsAst, t as createTable } from "./ddl-DrtjQMFK.mjs";
|
|
3
|
+
import { n as escapeLiteral, r as quoteIdentifier } from "./sql-utils-CV8Bdgtc.mjs";
|
|
4
|
+
import { t as buildTargetDetails } from "./planner-target-details-H8z9TFDg.mjs";
|
|
5
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
6
|
+
import { errorUnfilledPlaceholder } from "@prisma-next/errors/migration";
|
|
7
|
+
import { TsExpression, jsonToTsSource } from "@prisma-next/ts-render";
|
|
8
|
+
import { REFERENTIAL_ACTION_SQL } from "@prisma-next/sql-contract/referential-action-sql";
|
|
9
|
+
//#region src/core/migrations/operations/shared.ts
|
|
10
|
+
function step(description, sql, params) {
|
|
11
|
+
return {
|
|
12
|
+
description,
|
|
13
|
+
sql,
|
|
14
|
+
...ifDefined("params", params)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Renders a single column's inline DDL fragment within a `CREATE TABLE`
|
|
19
|
+
* statement. Honours the `inlineAutoincrementPrimaryKey` flag — SQLite
|
|
20
|
+
* treats `INTEGER PRIMARY KEY AUTOINCREMENT` as a special form that aliases
|
|
21
|
+
* `rowid`, and the column must not carry a `DEFAULT` or repeat `NOT NULL`.
|
|
22
|
+
*/
|
|
23
|
+
function renderColumnDefinition(column) {
|
|
24
|
+
const parts = [quoteIdentifier(column.name), column.typeSql];
|
|
25
|
+
if (column.inlineAutoincrementPrimaryKey) parts.push("PRIMARY KEY AUTOINCREMENT");
|
|
26
|
+
else {
|
|
27
|
+
if (column.defaultSql) parts.push(column.defaultSql);
|
|
28
|
+
if (!column.nullable) parts.push("NOT NULL");
|
|
29
|
+
}
|
|
30
|
+
return parts.join(" ");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Renders an inline FOREIGN KEY constraint clause for a `CREATE TABLE`
|
|
34
|
+
* body. Returns the empty string when `constraint` is false (the FK is
|
|
35
|
+
* tracked at the contract level for index-creation purposes only and must
|
|
36
|
+
* not produce DDL).
|
|
37
|
+
*/
|
|
38
|
+
function renderForeignKeyClause(fk) {
|
|
39
|
+
if (!fk.constraint) return "";
|
|
40
|
+
let sql = `${fk.name ? `CONSTRAINT ${quoteIdentifier(fk.name)} ` : ""}FOREIGN KEY (${fk.columns.map(quoteIdentifier).join(", ")}) REFERENCES ${quoteIdentifier(fk.references.table)} (${fk.references.columns.map(quoteIdentifier).join(", ")})`;
|
|
41
|
+
if (fk.onDelete !== void 0) sql += ` ON DELETE ${REFERENTIAL_ACTION_SQL[fk.onDelete]}`;
|
|
42
|
+
if (fk.onUpdate !== void 0) sql += ` ON UPDATE ${REFERENTIAL_ACTION_SQL[fk.onUpdate]}`;
|
|
43
|
+
return sql;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/core/migrations/operations/columns.ts
|
|
47
|
+
function addColumnExecuteSql(tableName, column) {
|
|
48
|
+
return [
|
|
49
|
+
`ALTER TABLE ${quoteIdentifier(tableName)}`,
|
|
50
|
+
`ADD COLUMN ${quoteIdentifier(column.name)} ${column.typeSql}`,
|
|
51
|
+
column.defaultSql,
|
|
52
|
+
column.nullable ? "" : "NOT NULL"
|
|
53
|
+
].filter(Boolean).join(" ");
|
|
54
|
+
}
|
|
55
|
+
function dropColumnExecuteSql(tableName, columnName) {
|
|
56
|
+
return `ALTER TABLE ${quoteIdentifier(tableName)} DROP COLUMN ${quoteIdentifier(columnName)}`;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/core/migrations/planner-ddl-builders.ts
|
|
60
|
+
const SAFE_NATIVE_TYPE_PATTERN = /^[a-zA-Z][a-zA-Z0-9_ ]*$/;
|
|
61
|
+
function assertSafeNativeType(nativeType) {
|
|
62
|
+
if (!SAFE_NATIVE_TYPE_PATTERN.test(nativeType)) throw new Error(`Unsafe native type name in contract: "${nativeType}". Native type names must match /^[a-zA-Z][a-zA-Z0-9_ ]*\$/`);
|
|
63
|
+
}
|
|
64
|
+
function assertSafeDefaultExpression(expression) {
|
|
65
|
+
if (expression.includes(";") || /--|\/\*|\bSELECT\b/i.test(expression)) throw new Error(`Unsafe default expression in contract: "${expression}". Default expressions must not contain semicolons, SQL comment tokens, or subqueries.`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Renders the column's DDL type token (e.g. `"INTEGER"`, `"TEXT"`).
|
|
69
|
+
* Resolves `typeRef` against `storageTypes` and validates the resulting
|
|
70
|
+
* native type against a safe-identifier pattern.
|
|
71
|
+
*/
|
|
72
|
+
function buildColumnTypeSql(column, storageTypes = {}) {
|
|
73
|
+
const resolved = resolveColumnTypeMetadata(column, storageTypes);
|
|
74
|
+
assertSafeNativeType(resolved.nativeType);
|
|
75
|
+
return resolved.nativeType.toUpperCase();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Renders the column's `DEFAULT …` clause. Returns the empty string when
|
|
79
|
+
* there is no default, and also when the default is `autoincrement()` —
|
|
80
|
+
* SQLite encodes that as `INTEGER PRIMARY KEY AUTOINCREMENT` inline on the
|
|
81
|
+
* column definition, not as a separate DEFAULT.
|
|
82
|
+
*/
|
|
83
|
+
function buildColumnDefaultSql(columnDefault) {
|
|
84
|
+
if (!columnDefault) return "";
|
|
85
|
+
switch (columnDefault.kind) {
|
|
86
|
+
case "literal": return `DEFAULT ${renderDefaultLiteral(columnDefault.value)}`;
|
|
87
|
+
case "function":
|
|
88
|
+
if (columnDefault.expression === "autoincrement()") return "";
|
|
89
|
+
if (columnDefault.expression === "now()") return "DEFAULT (datetime('now'))";
|
|
90
|
+
assertSafeDefaultExpression(columnDefault.expression);
|
|
91
|
+
return `DEFAULT (${columnDefault.expression})`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function renderDefaultLiteral(value) {
|
|
95
|
+
if (value instanceof Date) return `'${escapeLiteral(value.toISOString())}'`;
|
|
96
|
+
if (typeof value === "string") return `'${escapeLiteral(value)}'`;
|
|
97
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
98
|
+
if (typeof value === "boolean") return value ? "1" : "0";
|
|
99
|
+
if (value === null) return "NULL";
|
|
100
|
+
return `'${escapeLiteral(JSON.stringify(value))}'`;
|
|
101
|
+
}
|
|
102
|
+
function buildCreateIndexSql(tableName, indexName, columns, unique = false) {
|
|
103
|
+
return `CREATE ${unique ? "UNIQUE " : ""}INDEX ${quoteIdentifier(indexName)} ON ${quoteIdentifier(tableName)} (${columns.map(quoteIdentifier).join(", ")})`;
|
|
104
|
+
}
|
|
105
|
+
function buildDropIndexSql(indexName) {
|
|
106
|
+
return `DROP INDEX IF EXISTS ${quoteIdentifier(indexName)}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* True when the column is rendered inline as `INTEGER PRIMARY KEY
|
|
110
|
+
* AUTOINCREMENT`. Requires the column's default to be `autoincrement()` and
|
|
111
|
+
* the column to be the sole member of the table's primary key — anything
|
|
112
|
+
* else falls back to a separate PRIMARY KEY constraint with a default
|
|
113
|
+
* AUTOINCREMENT semantics expressed elsewhere.
|
|
114
|
+
*/
|
|
115
|
+
function isInlineAutoincrementPrimaryKey(table, columnName) {
|
|
116
|
+
if (table.primaryKey?.columns.length !== 1) return false;
|
|
117
|
+
if (table.primaryKey.columns[0] !== columnName) return false;
|
|
118
|
+
const column = table.columns[columnName];
|
|
119
|
+
return column?.default?.kind === "function" && column.default.expression === "autoincrement()";
|
|
120
|
+
}
|
|
121
|
+
function resolveColumnTypeMetadata(column, storageTypes) {
|
|
122
|
+
if (!column.typeRef) return column;
|
|
123
|
+
const referencedType = storageTypes[column.typeRef];
|
|
124
|
+
if (!referencedType) throw new Error(`Storage type "${column.typeRef}" referenced by column is not defined in storage.types.`);
|
|
125
|
+
return {
|
|
126
|
+
codecId: referencedType.codecId,
|
|
127
|
+
nativeType: referencedType.nativeType,
|
|
128
|
+
typeParams: referencedType.typeParams
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/core/migrations/operations/tables.ts
|
|
133
|
+
async function tableExistsSteps(lowerer, tableName) {
|
|
134
|
+
const checks = tableExistsAst(tableName);
|
|
135
|
+
return {
|
|
136
|
+
present: await lowerer.lowerToExecuteRequest(checks.tablePresent()),
|
|
137
|
+
absent: await lowerer.lowerToExecuteRequest(checks.tableAbsent())
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Renders the body of a `CREATE TABLE <name> ( … )` statement from a flat
|
|
142
|
+
* `SqliteTableSpec`. SQLite's `INTEGER PRIMARY KEY AUTOINCREMENT` form is
|
|
143
|
+
* inline on the column; the table-level PRIMARY KEY clause is emitted only
|
|
144
|
+
* when no column carries `inlineAutoincrementPrimaryKey`.
|
|
145
|
+
*/
|
|
146
|
+
function renderCreateTableSql(tableName, spec) {
|
|
147
|
+
const columnDefs = spec.columns.map(renderColumnDefinition);
|
|
148
|
+
const constraintDefs = [];
|
|
149
|
+
const hasInlinePk = spec.columns.some((c) => c.inlineAutoincrementPrimaryKey);
|
|
150
|
+
if (spec.primaryKey && !hasInlinePk) constraintDefs.push(`PRIMARY KEY (${spec.primaryKey.columns.map(quoteIdentifier).join(", ")})`);
|
|
151
|
+
for (const u of spec.uniques ?? []) {
|
|
152
|
+
const name = u.name ? `CONSTRAINT ${quoteIdentifier(u.name)} ` : "";
|
|
153
|
+
constraintDefs.push(`${name}UNIQUE (${u.columns.map(quoteIdentifier).join(", ")})`);
|
|
154
|
+
}
|
|
155
|
+
for (const fk of spec.foreignKeys ?? []) {
|
|
156
|
+
const clause = renderForeignKeyClause(fk);
|
|
157
|
+
if (clause) constraintDefs.push(clause);
|
|
158
|
+
}
|
|
159
|
+
const allDefs = [...columnDefs, ...constraintDefs];
|
|
160
|
+
return `CREATE TABLE ${quoteIdentifier(tableName)} (\n ${allDefs.join(",\n ")}\n)`;
|
|
161
|
+
}
|
|
162
|
+
async function recreateTable(args, lowerer) {
|
|
163
|
+
const { tableName, contractTable, schemaColumnNames, indexes, summary, postchecks, operationClass } = args;
|
|
164
|
+
const tempName = `_prisma_new_${tableName}`;
|
|
165
|
+
const liveSet = new Set(schemaColumnNames);
|
|
166
|
+
const sharedColumns = contractTable.columns.filter((c) => liveSet.has(c.name)).map((c) => c.name);
|
|
167
|
+
const columnList = sharedColumns.map(quoteIdentifier).join(", ");
|
|
168
|
+
const indexStatements = indexes.map((idx) => ({
|
|
169
|
+
description: `recreate index "${idx.name}" on "${tableName}"`,
|
|
170
|
+
sql: buildCreateIndexSql(tableName, idx.name, idx.columns)
|
|
171
|
+
}));
|
|
172
|
+
const copyStep = sharedColumns.length > 0 ? [step(`copy data from "${tableName}" to "${tempName}"`, `INSERT INTO ${quoteIdentifier(tempName)} (${columnList}) SELECT ${columnList} FROM ${quoteIdentifier(tableName)}`)] : [];
|
|
173
|
+
const tableSteps = await tableExistsSteps(lowerer, tableName);
|
|
174
|
+
const tempSteps = await tableExistsSteps(lowerer, tempName);
|
|
175
|
+
return {
|
|
176
|
+
id: `recreateTable.${tableName}`,
|
|
177
|
+
label: `Recreate table ${tableName}`,
|
|
178
|
+
summary,
|
|
179
|
+
operationClass,
|
|
180
|
+
target: {
|
|
181
|
+
id: "sqlite",
|
|
182
|
+
details: buildTargetDetails("table", tableName)
|
|
183
|
+
},
|
|
184
|
+
precheck: [step(`ensure table "${tableName}" exists`, tableSteps.present.sql, tableSteps.present.params), step(`ensure temp table "${tempName}" does not exist`, tempSteps.absent.sql, tempSteps.absent.params)],
|
|
185
|
+
execute: [
|
|
186
|
+
step(`create new table "${tempName}" with desired schema`, renderCreateTableSql(tempName, contractTable)),
|
|
187
|
+
...copyStep,
|
|
188
|
+
step(`drop old table "${tableName}"`, `DROP TABLE ${quoteIdentifier(tableName)}`),
|
|
189
|
+
step(`rename "${tempName}" to "${tableName}"`, `ALTER TABLE ${quoteIdentifier(tempName)} RENAME TO ${quoteIdentifier(tableName)}`),
|
|
190
|
+
...indexStatements
|
|
191
|
+
],
|
|
192
|
+
postcheck: [
|
|
193
|
+
step(`verify table "${tableName}" exists`, tableSteps.present.sql, tableSteps.present.params),
|
|
194
|
+
step(`verify temp table "${tempName}" is gone`, tempSteps.absent.sql, tempSteps.absent.params),
|
|
195
|
+
...postchecks
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Build a one-line summary of a recreate-table operation from the schema
|
|
201
|
+
* issues that triggered it. Lives next to `recreateTable` so the planner
|
|
202
|
+
* (which has the issues) can produce the same description the factory
|
|
203
|
+
* used to build inline. Keeping the formatting target-side keeps
|
|
204
|
+
* `RecreateTableCall` issue-free at the IR layer.
|
|
205
|
+
*/
|
|
206
|
+
function buildRecreateSummary(tableName, issues) {
|
|
207
|
+
return `Recreates table ${tableName} to apply schema changes: ${issues.map((i) => i.message).join("; ")}`;
|
|
208
|
+
}
|
|
209
|
+
const COLUMN_LEVEL_ISSUE_KINDS = new Set([
|
|
210
|
+
"nullability_mismatch",
|
|
211
|
+
"default_mismatch",
|
|
212
|
+
"default_missing",
|
|
213
|
+
"extra_default",
|
|
214
|
+
"type_mismatch"
|
|
215
|
+
]);
|
|
216
|
+
const PK_ISSUE_KINDS = new Set(["primary_key_mismatch", "extra_primary_key"]);
|
|
217
|
+
const UNIQUE_ISSUE_KINDS = new Set(["unique_constraint_mismatch", "extra_unique_constraint"]);
|
|
218
|
+
const FK_ISSUE_KINDS = new Set(["foreign_key_mismatch", "extra_foreign_key"]);
|
|
219
|
+
/**
|
|
220
|
+
* Returns the columns the contract expects as the table's primary key. Picks
|
|
221
|
+
* up SQLite's inline `INTEGER PRIMARY KEY AUTOINCREMENT` form when no
|
|
222
|
+
* explicit `primaryKey` clause is set on the spec.
|
|
223
|
+
*/
|
|
224
|
+
function expectedPrimaryKeyColumns(spec) {
|
|
225
|
+
if (spec.primaryKey) return spec.primaryKey.columns;
|
|
226
|
+
const inlinePk = spec.columns.find((c) => c.inlineAutoincrementPrimaryKey);
|
|
227
|
+
return inlinePk ? [inlinePk.name] : [];
|
|
228
|
+
}
|
|
229
|
+
function quoteSqlList(values) {
|
|
230
|
+
return values.map((v) => `'${escapeLiteral(v)}'`).join(", ");
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Per-issue postchecks verifying the recreated table's shape against the
|
|
234
|
+
* contract spec. Column-level issues (`nullability_mismatch`,
|
|
235
|
+
* `default_mismatch`, …) emit one targeted check each; constraint-level
|
|
236
|
+
* issues (`primary_key_mismatch`, `unique_constraint_mismatch`,
|
|
237
|
+
* `foreign_key_mismatch`, plus their `extra_*` siblings) emit one
|
|
238
|
+
* `pragma_*`-driven check per declared constraint in the contract spec, so
|
|
239
|
+
* a recreated table with the right columns but the wrong PK / unique / FK
|
|
240
|
+
* shape fails the postcheck instead of passing silently. Exported so the
|
|
241
|
+
* planner can pre-build the list at construction time and
|
|
242
|
+
* `RecreateTableCall` doesn't have to carry `SchemaIssue` objects through
|
|
243
|
+
* to render time.
|
|
244
|
+
*/
|
|
245
|
+
function buildRecreatePostchecks(tableName, issues, spec) {
|
|
246
|
+
const checks = [];
|
|
247
|
+
const t = escapeLiteral(tableName);
|
|
248
|
+
const byName = new Map(spec.columns.map((c) => [c.name, c]));
|
|
249
|
+
for (const issue of issues) {
|
|
250
|
+
if (issue.kind === "enum_values_changed") continue;
|
|
251
|
+
if (!COLUMN_LEVEL_ISSUE_KINDS.has(issue.kind)) continue;
|
|
252
|
+
if (!issue.column) continue;
|
|
253
|
+
const c = escapeLiteral(issue.column);
|
|
254
|
+
if (issue.kind === "nullability_mismatch") {
|
|
255
|
+
let wantNotNull;
|
|
256
|
+
if (issue.expected === "false") wantNotNull = true;
|
|
257
|
+
else if (issue.expected === "true") wantNotNull = false;
|
|
258
|
+
if (wantNotNull !== void 0) checks.push({
|
|
259
|
+
description: `verify "${issue.column}" nullability on "${tableName}"`,
|
|
260
|
+
sql: `SELECT COUNT(*) > 0 FROM pragma_table_info('${t}') WHERE name = '${c}' AND "notnull" = ${wantNotNull ? 1 : 0}`
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (issue.kind === "default_mismatch" || issue.kind === "default_missing") {
|
|
264
|
+
const colSpec = byName.get(issue.column);
|
|
265
|
+
const expectedRaw = colSpec?.defaultSql.startsWith("DEFAULT ") ? stripOuterParens(colSpec.defaultSql.slice(8)) : null;
|
|
266
|
+
if (expectedRaw) checks.push({
|
|
267
|
+
description: `verify "${issue.column}" default on "${tableName}"`,
|
|
268
|
+
sql: `SELECT COUNT(*) > 0 FROM pragma_table_info('${t}') WHERE name = '${c}' AND dflt_value = '${escapeLiteral(expectedRaw)}'`
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (issue.kind === "type_mismatch") {
|
|
272
|
+
const colSpec = byName.get(issue.column);
|
|
273
|
+
if (colSpec) checks.push({
|
|
274
|
+
description: `verify "${issue.column}" type on "${tableName}"`,
|
|
275
|
+
sql: `SELECT COUNT(*) > 0 FROM pragma_table_info('${t}') WHERE name = '${c}' AND LOWER(type) = '${escapeLiteral(colSpec.typeSql.toLowerCase())}'`
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (issue.kind === "extra_default") checks.push({
|
|
279
|
+
description: `verify "${issue.column}" has no default on "${tableName}"`,
|
|
280
|
+
sql: `SELECT COUNT(*) > 0 FROM pragma_table_info('${t}') WHERE name = '${c}' AND dflt_value IS NULL`
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const hasPkIssue = issues.some((i) => PK_ISSUE_KINDS.has(i.kind));
|
|
284
|
+
const hasUniqueIssue = issues.some((i) => UNIQUE_ISSUE_KINDS.has(i.kind));
|
|
285
|
+
const hasFkIssue = issues.some((i) => FK_ISSUE_KINDS.has(i.kind));
|
|
286
|
+
if (hasPkIssue) {
|
|
287
|
+
const pkColumns = expectedPrimaryKeyColumns(spec);
|
|
288
|
+
const colCount = pkColumns.length;
|
|
289
|
+
if (colCount === 0) checks.push({
|
|
290
|
+
description: `verify "${tableName}" has no primary key`,
|
|
291
|
+
sql: `SELECT (SELECT COUNT(*) FROM pragma_table_info('${t}') WHERE pk > 0) = 0`
|
|
292
|
+
});
|
|
293
|
+
else checks.push({
|
|
294
|
+
description: `verify primary key on "${tableName}"`,
|
|
295
|
+
sql: `SELECT (SELECT COUNT(*) FROM pragma_table_info('${t}') WHERE pk > 0) = ${colCount} AND (SELECT COUNT(*) FROM pragma_table_info('${t}') WHERE pk > 0 AND name IN (${quoteSqlList(pkColumns)})) = ${colCount}`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
if (hasUniqueIssue) for (const u of spec.uniques ?? []) {
|
|
299
|
+
const colCount = u.columns.length;
|
|
300
|
+
const description = u.name ? `verify unique constraint "${u.name}" on "${tableName}"` : `verify unique constraint (${u.columns.join(", ")}) on "${tableName}"`;
|
|
301
|
+
checks.push({
|
|
302
|
+
description,
|
|
303
|
+
sql: `SELECT EXISTS (SELECT 1 FROM pragma_index_list('${t}') l WHERE l."unique" = 1 AND (SELECT COUNT(*) FROM pragma_index_info(l.name)) = ${colCount} AND (SELECT COUNT(*) FROM pragma_index_info(l.name) WHERE name IN (${quoteSqlList(u.columns)})) = ${colCount})`
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
if (hasFkIssue) for (const fk of spec.foreignKeys ?? []) {
|
|
307
|
+
const refTable = escapeLiteral(fk.references.table);
|
|
308
|
+
const colCount = fk.columns.length;
|
|
309
|
+
const tuples = fk.columns.map((from, i) => {
|
|
310
|
+
const to = fk.references.columns[i] ?? from;
|
|
311
|
+
return `('${escapeLiteral(from)}', '${escapeLiteral(to)}')`;
|
|
312
|
+
}).join(", ");
|
|
313
|
+
const description = `verify foreign key (${fk.columns.join(", ")}) → ${fk.references.table}(${fk.references.columns.join(", ")}) on "${tableName}"`;
|
|
314
|
+
checks.push({
|
|
315
|
+
description,
|
|
316
|
+
sql: `SELECT EXISTS (SELECT 1 FROM pragma_foreign_key_list('${t}') f WHERE f."table" = '${refTable}' GROUP BY f.id HAVING COUNT(*) = ${colCount} AND SUM(CASE WHEN (f."from", f."to") IN (${tuples}) THEN 1 ELSE 0 END) = ${colCount})`
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return checks;
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/core/migrations/op-factory-call.ts
|
|
323
|
+
/**
|
|
324
|
+
* SQLite migration IR: one concrete `*Call` class per pure factory under
|
|
325
|
+
* `operations/`, plus a shared `SqliteOpFactoryCallNode` abstract base.
|
|
326
|
+
*
|
|
327
|
+
* Each call class carries fully-resolved literal arguments. `CreateTableCall`
|
|
328
|
+
* holds structured `DdlColumn[]` + `DdlTableConstraint[]` and lowers via the
|
|
329
|
+
* adapter's DDL path; other call classes carry flat SQL fragments. Codec /
|
|
330
|
+
* `typeRef` / default expansion happens upstream in the issue-planner /
|
|
331
|
+
* strategies, mirroring the Postgres `ColumnSpec` pattern.
|
|
332
|
+
*/
|
|
333
|
+
const TARGET_MIGRATION_MODULE = "@prisma-next/sqlite/migration";
|
|
334
|
+
var SqliteOpFactoryCallNode = class extends TsExpression {
|
|
335
|
+
importRequirements() {
|
|
336
|
+
return [{
|
|
337
|
+
moduleSpecifier: TARGET_MIGRATION_MODULE,
|
|
338
|
+
symbol: this.factoryName
|
|
339
|
+
}];
|
|
340
|
+
}
|
|
341
|
+
freeze() {
|
|
342
|
+
Object.freeze(this);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
function renderDdlColumnDefault(def) {
|
|
346
|
+
if (!def) return "";
|
|
347
|
+
if (def.kind === "literal") return `lit(${jsonToTsSource(def.value)})`;
|
|
348
|
+
return `fn(${jsonToTsSource(def.expression)})`;
|
|
349
|
+
}
|
|
350
|
+
function renderDdlColumnAsTsCall(column) {
|
|
351
|
+
const opts = [];
|
|
352
|
+
if (column.notNull) opts.push("notNull: true");
|
|
353
|
+
if (column.primaryKey) opts.push("primaryKey: true");
|
|
354
|
+
if (column.default) opts.push(`default: ${renderDdlColumnDefault(column.default)}`);
|
|
355
|
+
const optsStr = opts.length > 0 ? `, { ${opts.join(", ")} }` : "";
|
|
356
|
+
return `col(${jsonToTsSource(column.name)}, ${jsonToTsSource(column.type)}${optsStr})`;
|
|
357
|
+
}
|
|
358
|
+
function renderDdlConstraintAsTsCall(constraint) {
|
|
359
|
+
switch (constraint.kind) {
|
|
360
|
+
case "primary-key": {
|
|
361
|
+
const nameOpt = constraint.name ? `, { name: ${jsonToTsSource(constraint.name)} }` : "";
|
|
362
|
+
return `primaryKey(${jsonToTsSource(constraint.columns)}${nameOpt})`;
|
|
363
|
+
}
|
|
364
|
+
case "foreign-key": {
|
|
365
|
+
const opts = [];
|
|
366
|
+
if (constraint.name) opts.push(`name: ${jsonToTsSource(constraint.name)}`);
|
|
367
|
+
if (constraint.onDelete) opts.push(`onDelete: ${jsonToTsSource(constraint.onDelete)}`);
|
|
368
|
+
if (constraint.onUpdate) opts.push(`onUpdate: ${jsonToTsSource(constraint.onUpdate)}`);
|
|
369
|
+
const optsStr = opts.length > 0 ? `, { ${opts.join(", ")} }` : "";
|
|
370
|
+
return `foreignKey(${jsonToTsSource(constraint.columns)}, ${jsonToTsSource(constraint.refTable)}, ${jsonToTsSource(constraint.refColumns)}${optsStr})`;
|
|
371
|
+
}
|
|
372
|
+
case "unique": {
|
|
373
|
+
const nameOpt = constraint.name ? `, { name: ${jsonToTsSource(constraint.name)} }` : "";
|
|
374
|
+
return `unique(${jsonToTsSource(constraint.columns)}${nameOpt})`;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function constraintImportSymbols(constraints) {
|
|
379
|
+
if (!constraints || constraints.length === 0) return [];
|
|
380
|
+
const symbols = /* @__PURE__ */ new Set();
|
|
381
|
+
for (const c of constraints) if (c.kind === "primary-key") symbols.add("primaryKey");
|
|
382
|
+
else if (c.kind === "foreign-key") symbols.add("foreignKey");
|
|
383
|
+
else if (c.kind === "unique") symbols.add("unique");
|
|
384
|
+
return [...symbols];
|
|
385
|
+
}
|
|
386
|
+
function defaultImportSymbols(columns) {
|
|
387
|
+
const symbols = /* @__PURE__ */ new Set();
|
|
388
|
+
for (const col of columns) if (col.default?.kind === "literal") symbols.add("lit");
|
|
389
|
+
else if (col.default?.kind === "function") symbols.add("fn");
|
|
390
|
+
return [...symbols];
|
|
391
|
+
}
|
|
392
|
+
var CreateTableCall = class extends SqliteOpFactoryCallNode {
|
|
393
|
+
factoryName = "createTable";
|
|
394
|
+
operationClass = "additive";
|
|
395
|
+
tableName;
|
|
396
|
+
columns;
|
|
397
|
+
constraints;
|
|
398
|
+
label;
|
|
399
|
+
constructor(tableName, columns, constraints) {
|
|
400
|
+
super();
|
|
401
|
+
this.tableName = tableName;
|
|
402
|
+
this.columns = Object.freeze([...columns]);
|
|
403
|
+
this.constraints = constraints ? Object.freeze([...constraints]) : void 0;
|
|
404
|
+
this.label = `Create table ${tableName}`;
|
|
405
|
+
this.freeze();
|
|
406
|
+
}
|
|
407
|
+
async toOp(lowerer) {
|
|
408
|
+
if (lowerer === void 0) throw new Error(`CreateTableCall.toOp: a DDL lowerer is required on the SQLite planner path (table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`);
|
|
409
|
+
const ddlNode = createTable({
|
|
410
|
+
table: this.tableName,
|
|
411
|
+
columns: this.columns,
|
|
412
|
+
...ifDefined("constraints", this.constraints)
|
|
413
|
+
});
|
|
414
|
+
const statement = await lowerer.lowerToExecuteRequest(ddlNode);
|
|
415
|
+
const tableName = this.tableName;
|
|
416
|
+
const tableChecks = tableExistsAst(tableName);
|
|
417
|
+
const absent = await lowerer.lowerToExecuteRequest(tableChecks.tableAbsent());
|
|
418
|
+
const present = await lowerer.lowerToExecuteRequest(tableChecks.tablePresent());
|
|
419
|
+
return {
|
|
420
|
+
id: `table.${tableName}`,
|
|
421
|
+
label: `Create table ${tableName}`,
|
|
422
|
+
summary: `Creates table ${tableName} with required columns`,
|
|
423
|
+
operationClass: "additive",
|
|
424
|
+
target: {
|
|
425
|
+
id: "sqlite",
|
|
426
|
+
details: buildTargetDetails("table", tableName)
|
|
427
|
+
},
|
|
428
|
+
precheck: [step(`ensure table "${tableName}" does not exist`, absent.sql, absent.params)],
|
|
429
|
+
execute: [{
|
|
430
|
+
description: `create table "${tableName}"`,
|
|
431
|
+
sql: statement.sql,
|
|
432
|
+
params: statement.params ?? []
|
|
433
|
+
}],
|
|
434
|
+
postcheck: [step(`verify table "${tableName}" exists`, present.sql, present.params)]
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
renderTypeScript() {
|
|
438
|
+
const columnsList = this.columns.map(renderDdlColumnAsTsCall).join(", ");
|
|
439
|
+
const constraintsList = this.constraints ? this.constraints.map(renderDdlConstraintAsTsCall).join(", ") : void 0;
|
|
440
|
+
const opts = [];
|
|
441
|
+
opts.push(`table: ${jsonToTsSource(this.tableName)}`);
|
|
442
|
+
opts.push(`columns: [${columnsList}]`);
|
|
443
|
+
if (constraintsList) opts.push(`constraints: [${constraintsList}]`);
|
|
444
|
+
return `this.createTable({ ${opts.join(", ")} })`;
|
|
445
|
+
}
|
|
446
|
+
importRequirements() {
|
|
447
|
+
const req = [];
|
|
448
|
+
if (this.columns.length > 0) {
|
|
449
|
+
req.push({
|
|
450
|
+
moduleSpecifier: TARGET_MIGRATION_MODULE,
|
|
451
|
+
symbol: "col"
|
|
452
|
+
});
|
|
453
|
+
for (const sym of defaultImportSymbols(this.columns)) req.push({
|
|
454
|
+
moduleSpecifier: TARGET_MIGRATION_MODULE,
|
|
455
|
+
symbol: sym
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
for (const sym of constraintImportSymbols(this.constraints)) req.push({
|
|
459
|
+
moduleSpecifier: TARGET_MIGRATION_MODULE,
|
|
460
|
+
symbol: sym
|
|
461
|
+
});
|
|
462
|
+
return req;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
var DropTableCall = class extends SqliteOpFactoryCallNode {
|
|
466
|
+
factoryName = "dropTable";
|
|
467
|
+
operationClass = "destructive";
|
|
468
|
+
tableName;
|
|
469
|
+
label;
|
|
470
|
+
constructor(tableName) {
|
|
471
|
+
super();
|
|
472
|
+
this.tableName = tableName;
|
|
473
|
+
this.label = `Drop table ${tableName}`;
|
|
474
|
+
this.freeze();
|
|
475
|
+
}
|
|
476
|
+
async toOp(lowerer) {
|
|
477
|
+
if (lowerer === void 0) throw new Error(`DropTableCall.toOp: a lowerer is required on the SQLite planner path (table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`);
|
|
478
|
+
const checks = tableExistsAst(this.tableName);
|
|
479
|
+
const present = await lowerer.lowerToExecuteRequest(checks.tablePresent());
|
|
480
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.tableAbsent());
|
|
481
|
+
return {
|
|
482
|
+
id: `dropTable.${this.tableName}`,
|
|
483
|
+
label: `Drop table ${this.tableName}`,
|
|
484
|
+
summary: `Drops table ${this.tableName} which is not in the contract`,
|
|
485
|
+
operationClass: "destructive",
|
|
486
|
+
target: {
|
|
487
|
+
id: "sqlite",
|
|
488
|
+
details: buildTargetDetails("table", this.tableName)
|
|
489
|
+
},
|
|
490
|
+
precheck: [step(`ensure table "${this.tableName}" exists`, present.sql, present.params)],
|
|
491
|
+
execute: [step(`drop table "${this.tableName}"`, `DROP TABLE ${quoteIdentifier(this.tableName)}`)],
|
|
492
|
+
postcheck: [step(`verify table "${this.tableName}" is gone`, absent.sql, absent.params)]
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
renderTypeScript() {
|
|
496
|
+
return `this.dropTable({ table: ${jsonToTsSource(this.tableName)} })`;
|
|
497
|
+
}
|
|
498
|
+
importRequirements() {
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
var RecreateTableCall = class extends SqliteOpFactoryCallNode {
|
|
503
|
+
factoryName = "recreateTable";
|
|
504
|
+
operationClass;
|
|
505
|
+
tableName;
|
|
506
|
+
contractTable;
|
|
507
|
+
schemaColumnNames;
|
|
508
|
+
indexes;
|
|
509
|
+
summary;
|
|
510
|
+
postchecks;
|
|
511
|
+
label;
|
|
512
|
+
constructor(args) {
|
|
513
|
+
super();
|
|
514
|
+
this.tableName = args.tableName;
|
|
515
|
+
this.contractTable = args.contractTable;
|
|
516
|
+
this.schemaColumnNames = args.schemaColumnNames;
|
|
517
|
+
this.indexes = args.indexes;
|
|
518
|
+
this.summary = args.summary;
|
|
519
|
+
this.postchecks = args.postchecks;
|
|
520
|
+
this.operationClass = args.operationClass;
|
|
521
|
+
this.label = `Recreate table ${args.tableName}`;
|
|
522
|
+
this.freeze();
|
|
523
|
+
}
|
|
524
|
+
async toOp(lowerer) {
|
|
525
|
+
if (lowerer === void 0) throw new Error(`RecreateTableCall.toOp: a lowerer is required on the SQLite planner path (table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`);
|
|
526
|
+
return recreateTable({
|
|
527
|
+
tableName: this.tableName,
|
|
528
|
+
contractTable: this.contractTable,
|
|
529
|
+
schemaColumnNames: this.schemaColumnNames,
|
|
530
|
+
indexes: this.indexes,
|
|
531
|
+
summary: this.summary,
|
|
532
|
+
postchecks: this.postchecks,
|
|
533
|
+
operationClass: this.operationClass
|
|
534
|
+
}, lowerer);
|
|
535
|
+
}
|
|
536
|
+
renderTypeScript() {
|
|
537
|
+
return `this.recreateTable(${jsonToTsSource({
|
|
538
|
+
tableName: this.tableName,
|
|
539
|
+
contractTable: this.contractTable,
|
|
540
|
+
schemaColumnNames: this.schemaColumnNames,
|
|
541
|
+
indexes: this.indexes,
|
|
542
|
+
summary: this.summary,
|
|
543
|
+
postchecks: this.postchecks,
|
|
544
|
+
operationClass: this.operationClass
|
|
545
|
+
})})`;
|
|
546
|
+
}
|
|
547
|
+
importRequirements() {
|
|
548
|
+
return [];
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
var AddColumnCall = class extends SqliteOpFactoryCallNode {
|
|
552
|
+
factoryName = "addColumn";
|
|
553
|
+
operationClass = "additive";
|
|
554
|
+
tableName;
|
|
555
|
+
columnName;
|
|
556
|
+
column;
|
|
557
|
+
label;
|
|
558
|
+
constructor(tableName, column) {
|
|
559
|
+
super();
|
|
560
|
+
this.tableName = tableName;
|
|
561
|
+
this.columnName = column.name;
|
|
562
|
+
this.column = column;
|
|
563
|
+
this.label = `Add column ${column.name} on ${tableName}`;
|
|
564
|
+
this.freeze();
|
|
565
|
+
}
|
|
566
|
+
async toOp(lowerer) {
|
|
567
|
+
if (lowerer === void 0) throw new Error(`AddColumnCall.toOp: a lowerer is required on the SQLite planner path (column "${this.column.name}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`);
|
|
568
|
+
const checks = columnExistsAst(this.tableName, this.column.name);
|
|
569
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
|
|
570
|
+
const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
|
|
571
|
+
return {
|
|
572
|
+
id: `column.${this.tableName}.${this.column.name}`,
|
|
573
|
+
label: `Add column ${this.column.name} on ${this.tableName}`,
|
|
574
|
+
summary: `Adds column ${this.column.name} on ${this.tableName}`,
|
|
575
|
+
operationClass: "additive",
|
|
576
|
+
target: {
|
|
577
|
+
id: "sqlite",
|
|
578
|
+
details: buildTargetDetails("column", this.column.name, this.tableName)
|
|
579
|
+
},
|
|
580
|
+
precheck: [step(`ensure column "${this.column.name}" is missing`, absent.sql, absent.params)],
|
|
581
|
+
execute: [step(`add column "${this.column.name}"`, addColumnExecuteSql(this.tableName, this.column))],
|
|
582
|
+
postcheck: [step(`verify column "${this.column.name}" exists`, present.sql, present.params)]
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
renderTypeScript() {
|
|
586
|
+
return `this.addColumn({ table: ${jsonToTsSource(this.tableName)}, column: ${jsonToTsSource(this.column)} })`;
|
|
587
|
+
}
|
|
588
|
+
importRequirements() {
|
|
589
|
+
return [];
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
var DropColumnCall = class extends SqliteOpFactoryCallNode {
|
|
593
|
+
factoryName = "dropColumn";
|
|
594
|
+
operationClass = "destructive";
|
|
595
|
+
tableName;
|
|
596
|
+
columnName;
|
|
597
|
+
label;
|
|
598
|
+
constructor(tableName, columnName) {
|
|
599
|
+
super();
|
|
600
|
+
this.tableName = tableName;
|
|
601
|
+
this.columnName = columnName;
|
|
602
|
+
this.label = `Drop column ${columnName} on ${tableName}`;
|
|
603
|
+
this.freeze();
|
|
604
|
+
}
|
|
605
|
+
async toOp(lowerer) {
|
|
606
|
+
if (lowerer === void 0) throw new Error(`DropColumnCall.toOp: a lowerer is required on the SQLite planner path (column "${this.columnName}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`);
|
|
607
|
+
const checks = columnExistsAst(this.tableName, this.columnName);
|
|
608
|
+
const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
|
|
609
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
|
|
610
|
+
return {
|
|
611
|
+
id: `dropColumn.${this.tableName}.${this.columnName}`,
|
|
612
|
+
label: `Drop column ${this.columnName} on ${this.tableName}`,
|
|
613
|
+
summary: `Drops column ${this.columnName} on ${this.tableName} which is not in the contract`,
|
|
614
|
+
operationClass: "destructive",
|
|
615
|
+
target: {
|
|
616
|
+
id: "sqlite",
|
|
617
|
+
details: buildTargetDetails("column", this.columnName, this.tableName)
|
|
618
|
+
},
|
|
619
|
+
precheck: [step(`ensure column "${this.columnName}" exists on "${this.tableName}"`, present.sql, present.params)],
|
|
620
|
+
execute: [step(`drop column "${this.columnName}" from "${this.tableName}"`, dropColumnExecuteSql(this.tableName, this.columnName))],
|
|
621
|
+
postcheck: [step(`verify column "${this.columnName}" is gone from "${this.tableName}"`, absent.sql, absent.params)]
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
renderTypeScript() {
|
|
625
|
+
return `this.dropColumn({ table: ${jsonToTsSource(this.tableName)}, column: ${jsonToTsSource(this.columnName)} })`;
|
|
626
|
+
}
|
|
627
|
+
importRequirements() {
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
var CreateIndexCall = class extends SqliteOpFactoryCallNode {
|
|
632
|
+
factoryName = "createIndex";
|
|
633
|
+
operationClass = "additive";
|
|
634
|
+
tableName;
|
|
635
|
+
indexName;
|
|
636
|
+
columns;
|
|
637
|
+
label;
|
|
638
|
+
constructor(tableName, indexName, columns) {
|
|
639
|
+
super();
|
|
640
|
+
this.tableName = tableName;
|
|
641
|
+
this.indexName = indexName;
|
|
642
|
+
this.columns = columns;
|
|
643
|
+
this.label = `Create index ${indexName} on ${tableName}`;
|
|
644
|
+
this.freeze();
|
|
645
|
+
}
|
|
646
|
+
async toOp(lowerer) {
|
|
647
|
+
if (lowerer === void 0) throw new Error(`CreateIndexCall.toOp: a lowerer is required on the SQLite planner path (index "${this.indexName}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`);
|
|
648
|
+
const checks = indexExistsAst(this.indexName);
|
|
649
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.indexAbsent());
|
|
650
|
+
const present = await lowerer.lowerToExecuteRequest(checks.indexPresent());
|
|
651
|
+
return {
|
|
652
|
+
id: `index.${this.tableName}.${this.indexName}`,
|
|
653
|
+
label: `Create index ${this.indexName} on ${this.tableName}`,
|
|
654
|
+
summary: `Creates index ${this.indexName} on ${this.tableName}`,
|
|
655
|
+
operationClass: "additive",
|
|
656
|
+
target: {
|
|
657
|
+
id: "sqlite",
|
|
658
|
+
details: buildTargetDetails("index", this.indexName, this.tableName)
|
|
659
|
+
},
|
|
660
|
+
precheck: [step(`ensure index "${this.indexName}" is missing`, absent.sql, absent.params)],
|
|
661
|
+
execute: [step(`create index "${this.indexName}"`, buildCreateIndexSql(this.tableName, this.indexName, this.columns))],
|
|
662
|
+
postcheck: [step(`verify index "${this.indexName}" exists`, present.sql, present.params)]
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
renderTypeScript() {
|
|
666
|
+
return `this.createIndex({ table: ${jsonToTsSource(this.tableName)}, index: ${jsonToTsSource(this.indexName)}, columns: ${jsonToTsSource(this.columns)} })`;
|
|
667
|
+
}
|
|
668
|
+
importRequirements() {
|
|
669
|
+
return [];
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
var DropIndexCall = class extends SqliteOpFactoryCallNode {
|
|
673
|
+
factoryName = "dropIndex";
|
|
674
|
+
operationClass = "destructive";
|
|
675
|
+
tableName;
|
|
676
|
+
indexName;
|
|
677
|
+
label;
|
|
678
|
+
constructor(tableName, indexName) {
|
|
679
|
+
super();
|
|
680
|
+
this.tableName = tableName;
|
|
681
|
+
this.indexName = indexName;
|
|
682
|
+
this.label = `Drop index ${indexName} on ${tableName}`;
|
|
683
|
+
this.freeze();
|
|
684
|
+
}
|
|
685
|
+
async toOp(lowerer) {
|
|
686
|
+
if (lowerer === void 0) throw new Error(`DropIndexCall.toOp: a lowerer is required on the SQLite planner path (index "${this.indexName}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`);
|
|
687
|
+
const checks = indexExistsAst(this.indexName);
|
|
688
|
+
const present = await lowerer.lowerToExecuteRequest(checks.indexPresent());
|
|
689
|
+
const absent = await lowerer.lowerToExecuteRequest(checks.indexAbsent());
|
|
690
|
+
return {
|
|
691
|
+
id: `dropIndex.${this.tableName}.${this.indexName}`,
|
|
692
|
+
label: `Drop index ${this.indexName} on ${this.tableName}`,
|
|
693
|
+
summary: `Drops index ${this.indexName} on ${this.tableName} which is not in the contract`,
|
|
694
|
+
operationClass: "destructive",
|
|
695
|
+
target: {
|
|
696
|
+
id: "sqlite",
|
|
697
|
+
details: buildTargetDetails("index", this.indexName, this.tableName)
|
|
698
|
+
},
|
|
699
|
+
precheck: [step(`ensure index "${this.indexName}" exists`, present.sql, present.params)],
|
|
700
|
+
execute: [step(`drop index "${this.indexName}"`, buildDropIndexSql(this.indexName))],
|
|
701
|
+
postcheck: [step(`verify index "${this.indexName}" is gone`, absent.sql, absent.params)]
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
renderTypeScript() {
|
|
705
|
+
return `this.dropIndex({ table: ${jsonToTsSource(this.tableName)}, index: ${jsonToTsSource(this.indexName)} })`;
|
|
706
|
+
}
|
|
707
|
+
importRequirements() {
|
|
708
|
+
return [];
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
/**
|
|
712
|
+
* A planner-generated data-transform stub. The current default strategy
|
|
713
|
+
* (`nullabilityTighteningBackfillStrategy`) emits one of these with a
|
|
714
|
+
* backfill-flavored `id`/`label` when the policy allows `'data'` and the
|
|
715
|
+
* contract tightens a column's nullability, but the op itself is generic —
|
|
716
|
+
* any future strategy that needs a placeholder data step can construct one
|
|
717
|
+
* with its own id/label.
|
|
718
|
+
*
|
|
719
|
+
* `toOp()` always throws `PN-MIG-2001`: the planner cannot lower a stubbed
|
|
720
|
+
* transform to a runtime op — the user must edit the rendered
|
|
721
|
+
* `migration.ts` and re-emit.
|
|
722
|
+
*/
|
|
723
|
+
var DataTransformCall = class extends SqliteOpFactoryCallNode {
|
|
724
|
+
factoryName = "dataTransform";
|
|
725
|
+
operationClass = "data";
|
|
726
|
+
id;
|
|
727
|
+
label;
|
|
728
|
+
tableName;
|
|
729
|
+
columnName;
|
|
730
|
+
constructor(id, label, tableName, columnName) {
|
|
731
|
+
super();
|
|
732
|
+
this.id = id;
|
|
733
|
+
this.label = label;
|
|
734
|
+
this.tableName = tableName;
|
|
735
|
+
this.columnName = columnName;
|
|
736
|
+
this.freeze();
|
|
737
|
+
}
|
|
738
|
+
toOp(_lowerer) {
|
|
739
|
+
throw errorUnfilledPlaceholder(this.label);
|
|
740
|
+
}
|
|
741
|
+
renderTypeScript() {
|
|
742
|
+
const slot = `${this.tableName}-${this.columnName}-backfill-sql`;
|
|
743
|
+
return [
|
|
744
|
+
"dataTransform({",
|
|
745
|
+
` id: ${jsonToTsSource(this.id)},`,
|
|
746
|
+
` label: ${jsonToTsSource(this.label)},`,
|
|
747
|
+
` table: ${jsonToTsSource(this.tableName)},`,
|
|
748
|
+
` description: ${jsonToTsSource(`Backfill NULL ${this.columnName} values in ${this.tableName}`)},`,
|
|
749
|
+
` run: () => placeholder(${jsonToTsSource(slot)}),`,
|
|
750
|
+
"})"
|
|
751
|
+
].join("\n");
|
|
752
|
+
}
|
|
753
|
+
importRequirements() {
|
|
754
|
+
return [{
|
|
755
|
+
moduleSpecifier: TARGET_MIGRATION_MODULE,
|
|
756
|
+
symbol: this.factoryName
|
|
757
|
+
}, {
|
|
758
|
+
moduleSpecifier: TARGET_MIGRATION_MODULE,
|
|
759
|
+
symbol: "placeholder"
|
|
760
|
+
}];
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
/**
|
|
764
|
+
* Laundered pre-built operation. Mirrors Postgres's `RawSqlCall`: wraps an
|
|
765
|
+
* already-materialized `SqlMigrationPlanOperation` (typically produced by a
|
|
766
|
+
* SQL-family helper or a codec lifecycle hook) so the planner can carry it
|
|
767
|
+
* alongside structured call IR. `toOp()` returns the stored op unchanged;
|
|
768
|
+
* `renderTypeScript()` emits `rawSql({...})` with the op serialized as a
|
|
769
|
+
* JSON literal — round-tripping requires every field on the op to be
|
|
770
|
+
* JSON-serializable (no closures).
|
|
771
|
+
*/
|
|
772
|
+
var RawSqlCall = class extends SqliteOpFactoryCallNode {
|
|
773
|
+
factoryName = "rawSql";
|
|
774
|
+
operationClass;
|
|
775
|
+
label;
|
|
776
|
+
op;
|
|
777
|
+
constructor(op) {
|
|
778
|
+
super();
|
|
779
|
+
this.op = op;
|
|
780
|
+
this.label = op.label;
|
|
781
|
+
this.operationClass = op.operationClass;
|
|
782
|
+
this.freeze();
|
|
783
|
+
}
|
|
784
|
+
toOp(_lowerer) {
|
|
785
|
+
return this.op;
|
|
786
|
+
}
|
|
787
|
+
renderTypeScript() {
|
|
788
|
+
return `rawSql(${jsonToTsSource(this.op)})`;
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
//#endregion
|
|
792
|
+
export { step as _, DropColumnCall as a, RawSqlCall as c, buildRecreateSummary as d, buildColumnDefaultSql as f, resolveColumnTypeMetadata as g, renderDefaultLiteral as h, DataTransformCall as i, RecreateTableCall as l, isInlineAutoincrementPrimaryKey as m, CreateIndexCall as n, DropIndexCall as o, buildColumnTypeSql as p, CreateTableCall as r, DropTableCall as s, AddColumnCall as t, buildRecreatePostchecks as u };
|
|
793
|
+
|
|
794
|
+
//# sourceMappingURL=op-factory-call-DmdfD1yd.mjs.map
|