qubu 0.0.0 → 0.3.4
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/canonical-BbnqavJm.mjs +972 -0
- package/dist/codegen.d.mts +107 -0
- package/dist/codegen.mjs +1199 -0
- package/dist/column-CXMxx8Hq.mjs +118 -0
- package/dist/complete-D5Djh-zo.mjs +1788 -0
- package/dist/complete-types-BdFqUfbb.d.mts +371 -0
- package/dist/core.d.mts +33 -0
- package/dist/core.mjs +36 -0
- package/dist/ddl.d.mts +118 -0
- package/dist/ddl.mjs +1120 -0
- package/dist/dialect-b2-Z6uBF.mjs +13 -0
- package/dist/diff.d.mts +2 -0
- package/dist/diff.mjs +883 -0
- package/dist/drizzle-mysql.d.mts +24 -0
- package/dist/drizzle-mysql.mjs +72 -0
- package/dist/drizzle-postgres.d.mts +24 -0
- package/dist/drizzle-postgres.mjs +73 -0
- package/dist/drizzle-sqlite.d.mts +24 -0
- package/dist/drizzle-sqlite.mjs +82 -0
- package/dist/drizzle.d.mts +13 -0
- package/dist/drizzle.mjs +2 -0
- package/dist/errors-BGCoLe_r.mjs +14 -0
- package/dist/errors-Dxv73YJu.mjs +26 -0
- package/dist/explain-CkIK13L_.mjs +98 -0
- package/dist/index-Ds7-mhJi.d.mts +146 -0
- package/dist/index-Dug5HnLB.d.mts +204 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1276 -0
- package/dist/introspection.d.mts +117 -0
- package/dist/introspection.mjs +4341 -0
- package/dist/json-CUZlv4HT.mjs +169 -0
- package/dist/migration.d.mts +215 -0
- package/dist/migration.mjs +1161 -0
- package/dist/mysql-DqkqXB6A.mjs +355 -0
- package/dist/mysql.d.mts +5 -0
- package/dist/mysql.mjs +50 -0
- package/dist/naming-QVCOnSj2.mjs +20 -0
- package/dist/omit-OxV58AwX.mjs +5 -0
- package/dist/on-conflict-BxnxubMb.mjs +143 -0
- package/dist/postgres-DEBBeh52.mjs +235 -0
- package/dist/postgres.d.mts +123 -0
- package/dist/postgres.mjs +52 -0
- package/dist/registry-BufIskVN.mjs +569 -0
- package/dist/relational-DCZrrNia.mjs +77 -0
- package/dist/runtime-BTr-MTlo.mjs +193 -0
- package/dist/schema.d.mts +2 -0
- package/dist/schema.mjs +5 -0
- package/dist/serialize-PF1cfH2P.mjs +556 -0
- package/dist/snapshot-CWPgzxNx.mjs +392 -0
- package/dist/snapshot.d.mts +4 -0
- package/dist/snapshot.mjs +8 -0
- package/dist/source-DUoJVXmL.mjs +68 -0
- package/dist/sqlite-BU6DBxef.mjs +320 -0
- package/dist/sqlite.d.mts +5 -0
- package/dist/sqlite.mjs +48 -0
- package/dist/standard-BTVYKh_F.mjs +12 -0
- package/dist/table-llv9tsZ8.mjs +384 -0
- package/dist/types-4Q076HKo.d.mts +3636 -0
- package/dist/types-BX0mckiU.d.mts +644 -0
- package/dist/types-Cec0xzo4.mjs +116 -0
- package/dist/types-H4vyCw8_.d.mts +45 -0
- package/dist/value-BvilP0oz.mjs +29 -0
- package/dist/vite/ambient.d.ts +449 -0
- package/dist/vite.d.mts +35 -0
- package/dist/vite.mjs +498 -0
- package/docs/config.json +21 -0
- package/docs/dialects-and-execution.md +427 -0
- package/docs/getting-started.md +96 -0
- package/docs/guides/compose-queries.md +225 -0
- package/docs/guides/drizzle.md +130 -0
- package/docs/guides/extensions/dialects.md +40 -0
- package/docs/guides/extensions/overview.md +36 -0
- package/docs/guides/extensions/sources-and-clauses.md +82 -0
- package/docs/guides/extensions/typed-expressions.md +94 -0
- package/docs/guides/extensions/unsafe-syntax.md +27 -0
- package/docs/guides/json.md +65 -0
- package/docs/guides/mutations.md +138 -0
- package/docs/guides/select/conditions.md +110 -0
- package/docs/guides/select/grouping-and-windows.md +82 -0
- package/docs/guides/select/ordering-and-pagination.md +78 -0
- package/docs/guides/select/overview.md +139 -0
- package/docs/guides/sql-templates.md +157 -0
- package/docs/guides/vite-plugin.md +92 -0
- package/docs/index.md +111 -0
- package/docs/query-model/fragments.md +109 -0
- package/docs/query-model/result-shapes.md +125 -0
- package/docs/query-model/source-scope.md +182 -0
- package/docs/reference/introspection-support.md +196 -0
- package/docs/reference/mysql-snapshot.md +55 -0
- package/docs/reference/postgres-snapshot.md +61 -0
- package/docs/reference/sqlite-snapshot.md +53 -0
- package/docs/reference/supported-surface.md +104 -0
- package/docs/schema/catalog-model.md +58 -0
- package/docs/schema/code-generation.md +139 -0
- package/docs/schema/columns-and-writes.md +130 -0
- package/docs/schema/constraints-and-indexes.md +180 -0
- package/docs/schema/ddl-emission.md +75 -0
- package/docs/schema/diff.md +75 -0
- package/docs/schema/introspection.md +214 -0
- package/docs/schema/migration-plans.md +76 -0
- package/docs/schema/snapshots.md +93 -0
- package/docs/schema/storage-and-schema-sql.md +104 -0
- package/docs/schema/tables-and-names.md +106 -0
- package/docs/sql-semantic-types.md +141 -0
- package/docs/troubleshooting.md +151 -0
- package/package.json +132 -10
- package/skills/qubu/SKILL.md +42 -0
- package/skills/qubu/agents/openai.yaml +6 -0
- package/readme.md +0 -1
package/dist/ddl.mjs
ADDED
|
@@ -0,0 +1,1120 @@
|
|
|
1
|
+
import { n as sqliteSchemaDialect } from "./sqlite-BU6DBxef.mjs";
|
|
2
|
+
import { n as postgresSchemaDialect } from "./postgres-DEBBeh52.mjs";
|
|
3
|
+
import { n as mysqlSchemaDialect } from "./mysql-DqkqXB6A.mjs";
|
|
4
|
+
import { assertMigrationPlan } from "./migration.mjs";
|
|
5
|
+
//#region src/ddl/emitter.ts
|
|
6
|
+
const objectKinds = /* @__PURE__ */ new Set([
|
|
7
|
+
"namespace",
|
|
8
|
+
"table",
|
|
9
|
+
"column",
|
|
10
|
+
"constraint",
|
|
11
|
+
"index",
|
|
12
|
+
"view",
|
|
13
|
+
"materialized-view",
|
|
14
|
+
"sequence",
|
|
15
|
+
"enum",
|
|
16
|
+
"domain",
|
|
17
|
+
"collation",
|
|
18
|
+
"trigger",
|
|
19
|
+
"routine",
|
|
20
|
+
"partition",
|
|
21
|
+
"policy",
|
|
22
|
+
"extension",
|
|
23
|
+
"comment",
|
|
24
|
+
"ownership",
|
|
25
|
+
"deferred-object",
|
|
26
|
+
"opaque-object",
|
|
27
|
+
"custom-sql"
|
|
28
|
+
]);
|
|
29
|
+
const lockRank = {
|
|
30
|
+
none: 0,
|
|
31
|
+
shared: 1,
|
|
32
|
+
exclusive: 2
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Build the shared strict preflight and rendering boundary for one engine.
|
|
36
|
+
* The renderer only receives a validated MigrationPlan and a SchemaDialect.
|
|
37
|
+
*/
|
|
38
|
+
function createDdlEmitter(features) {
|
|
39
|
+
const emitter = {
|
|
40
|
+
dialect: features.dialect,
|
|
41
|
+
diagnose(plan, schemaDialect, options = {}) {
|
|
42
|
+
return preflight(plan, schemaDialect, options, features);
|
|
43
|
+
},
|
|
44
|
+
emit(plan, schemaDialect, options = {}) {
|
|
45
|
+
const diagnostics = preflight(plan, schemaDialect, options, features);
|
|
46
|
+
if (diagnostics.some((item) => item.severity === "error")) return emission(false, schemaDialect.name, [], diagnostics);
|
|
47
|
+
let validated;
|
|
48
|
+
try {
|
|
49
|
+
validated = assertMigrationPlan(plan);
|
|
50
|
+
} catch {
|
|
51
|
+
return emission(false, schemaDialect.name, [], diagnostics);
|
|
52
|
+
}
|
|
53
|
+
const statements = [];
|
|
54
|
+
try {
|
|
55
|
+
for (const operation of validated.operations) {
|
|
56
|
+
if (operation.status === "skipped") continue;
|
|
57
|
+
if (isCoveredByParent(operation, validated.operations)) continue;
|
|
58
|
+
const sql = renderOperation(operation, validated.operations, schemaDialect, features);
|
|
59
|
+
if (sql === void 0 || sql.length === 0) continue;
|
|
60
|
+
statements.push({
|
|
61
|
+
operationId: operation.id,
|
|
62
|
+
position: operation.position,
|
|
63
|
+
kind: operation.kind,
|
|
64
|
+
sql,
|
|
65
|
+
text: sql,
|
|
66
|
+
parameters: Object.freeze([])
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
const diagnostic = {
|
|
71
|
+
code: "malformed-operation",
|
|
72
|
+
severity: "error",
|
|
73
|
+
message: error instanceof Error ? error.message : String(error),
|
|
74
|
+
path: []
|
|
75
|
+
};
|
|
76
|
+
return emission(false, schemaDialect.name, [], [...diagnostics, diagnostic]);
|
|
77
|
+
}
|
|
78
|
+
return emission(true, schemaDialect.name, statements, diagnostics);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return Object.freeze(emitter);
|
|
82
|
+
}
|
|
83
|
+
function preflight(input, schemaDialect, options, features) {
|
|
84
|
+
const diagnostics = [];
|
|
85
|
+
let plan;
|
|
86
|
+
try {
|
|
87
|
+
plan = assertMigrationPlan(input);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const raw = isRecord(input) ? input : void 0;
|
|
90
|
+
if (raw?.ready === false && !options.allowBlocked && !options.allowUnsafe) diagnostics.push({
|
|
91
|
+
code: "blocked-plan",
|
|
92
|
+
severity: "error",
|
|
93
|
+
message: "Migration plan is blocked and needs explicit review before DDL emission",
|
|
94
|
+
path: []
|
|
95
|
+
});
|
|
96
|
+
if (Array.isArray(raw?.diagnostics)) for (const item of raw.diagnostics) {
|
|
97
|
+
if (!isRecord(item) || typeof item.code !== "string") continue;
|
|
98
|
+
const code = mapPlanDiagnosticCode(item.code);
|
|
99
|
+
if (options.allowUnsafe || code === "unknown" && options.allowUnknown || code === "lossy" && options.allowLossy || code === "unsupported" && options.allowUnsupported || code === "destructive" && options.allowDestructive || code === "decision-required" && options.allowDecisionRequired) continue;
|
|
100
|
+
diagnostics.push({
|
|
101
|
+
code,
|
|
102
|
+
severity: "error",
|
|
103
|
+
message: typeof item.message === "string" ? item.message : "Migration plan diagnostic",
|
|
104
|
+
path: isPath(item.path) ? item.path : [],
|
|
105
|
+
operationId: typeof item.operationId === "string" ? item.operationId : void 0
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const source = error;
|
|
109
|
+
if (Array.isArray(source.diagnostics)) for (const item of source.diagnostics) {
|
|
110
|
+
if (!isRecord(item)) continue;
|
|
111
|
+
diagnostics.push({
|
|
112
|
+
code: "invalid-plan",
|
|
113
|
+
severity: "error",
|
|
114
|
+
message: typeof item.message === "string" ? item.message : "Migration plan validation failed",
|
|
115
|
+
path: isPath(item.path) ? item.path : []
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (diagnostics.length === 0) diagnostics.push({
|
|
119
|
+
code: "invalid-plan",
|
|
120
|
+
severity: "error",
|
|
121
|
+
message: error instanceof Error ? error.message : String(error),
|
|
122
|
+
path: []
|
|
123
|
+
});
|
|
124
|
+
return sortDiagnostics(diagnostics);
|
|
125
|
+
}
|
|
126
|
+
if (plan.dialect.name !== features.dialect) diagnostics.push({
|
|
127
|
+
code: "dialect-mismatch",
|
|
128
|
+
severity: "error",
|
|
129
|
+
message: `Migration plan dialect "${plan.dialect.name}" does not match the ${features.dialect} DDL emitter`,
|
|
130
|
+
path: ["dialect", "name"],
|
|
131
|
+
dialect: plan.dialect.name
|
|
132
|
+
});
|
|
133
|
+
if (schemaDialect.name !== features.dialect) diagnostics.push({
|
|
134
|
+
code: "dialect-mismatch",
|
|
135
|
+
severity: "error",
|
|
136
|
+
message: `Schema dialect "${schemaDialect.name}" does not match the ${features.dialect} DDL emitter`,
|
|
137
|
+
path: ["dialect"],
|
|
138
|
+
dialect: schemaDialect.name
|
|
139
|
+
});
|
|
140
|
+
if (schemaDialect.schema.version !== plan.dialect.version) diagnostics.push({
|
|
141
|
+
code: "dialect-mismatch",
|
|
142
|
+
severity: "error",
|
|
143
|
+
message: `Schema dialect metadata version ${schemaDialect.schema.version} does not match plan dialect version ${plan.dialect.version}`,
|
|
144
|
+
path: ["dialect", "version"],
|
|
145
|
+
dialect: schemaDialect.name
|
|
146
|
+
});
|
|
147
|
+
if (!plan.ready && !options.allowBlocked && !options.allowUnsafe) diagnostics.push({
|
|
148
|
+
code: "blocked-plan",
|
|
149
|
+
severity: "error",
|
|
150
|
+
message: "Migration plan is blocked and needs explicit review before DDL emission",
|
|
151
|
+
path: []
|
|
152
|
+
});
|
|
153
|
+
for (const source of plan.diagnostics) {
|
|
154
|
+
const code = source.code;
|
|
155
|
+
if (options.allowUnsafe || code === "unknown" && options.allowUnknown || code === "lossy" && options.allowLossy || code === "unsupported" && options.allowUnsupported || code === "destructive" && options.allowDestructive || code === "decision-required" && options.allowDecisionRequired) continue;
|
|
156
|
+
const mapped = mapPlanDiagnosticCode(code);
|
|
157
|
+
diagnostics.push({
|
|
158
|
+
code: mapped,
|
|
159
|
+
severity: "error",
|
|
160
|
+
message: source.message,
|
|
161
|
+
path: source.path,
|
|
162
|
+
operationId: source.operationId,
|
|
163
|
+
kind: source.kind,
|
|
164
|
+
dialect: source.dialect?.name
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
const operationIds = new Set(plan.operations.map((operation) => operation.id));
|
|
168
|
+
for (const operation of plan.operations) {
|
|
169
|
+
if (operation.status === "skipped") continue;
|
|
170
|
+
if (operation.dialect.name !== features.dialect) diagnostics.push(operationDiagnostic(operation, "dialect-mismatch", `Operation dialect "${operation.dialect.name}" does not match the ${features.dialect} DDL emitter`));
|
|
171
|
+
if (operation.status === "decision-required" && !options.allowDecisionRequired && !options.allowUnsafe) diagnostics.push(operationDiagnostic(operation, "decision-required", "Operation needs an explicit safety decision before DDL emission"));
|
|
172
|
+
diagnoseSafety(operation, options, diagnostics);
|
|
173
|
+
diagnoseExecutionContext(operation, options, diagnostics);
|
|
174
|
+
if (operation.type === "custom-sql") {
|
|
175
|
+
if (operation.customSql === void 0 || operation.customSql.sql.trim().length === 0) diagnostics.push(operationDiagnostic(operation, "custom-sql", "Custom SQL operation has no SQL text"));
|
|
176
|
+
else if (operation.customSql.dialect.name !== features.dialect) diagnostics.push(operationDiagnostic(operation, "dialect-mismatch", `Custom SQL dialect "${operation.customSql.dialect.name}" does not match the ${features.dialect} DDL emitter`));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (!objectKinds.has(operation.kind)) diagnostics.push(operationDiagnostic(operation, "malformed-operation", `Unknown migration operation kind "${String(operation.kind)}"`));
|
|
180
|
+
if (operation.origin === void 0) diagnostics.push(operationDiagnostic(operation, "malformed-operation", "Non-custom operation is missing source origin data"));
|
|
181
|
+
const object = operation.origin?.after ?? operation.origin?.before;
|
|
182
|
+
if (object !== void 0 && !isRecord(object.value)) diagnostics.push(operationDiagnostic(operation, "malformed-operation", "Operation origin value must be an object"));
|
|
183
|
+
if (object !== void 0) {
|
|
184
|
+
const name = operationName(operation, object.value);
|
|
185
|
+
if (name === void 0 && requiresPhysicalName(operation.kind)) diagnostics.push(operationDiagnostic(operation, "malformed-operation", "Operation object is missing a non-empty physical name"));
|
|
186
|
+
if (name !== void 0 && name.trim().length === 0) diagnostics.push(operationDiagnostic(operation, "malformed-operation", "Operation physical name cannot be empty"));
|
|
187
|
+
}
|
|
188
|
+
diagnoseVersion(operation, options, features, diagnostics);
|
|
189
|
+
diagnoseOperation(operation, schemaDialect, features, diagnostics);
|
|
190
|
+
for (const dependency of operation.dependsOn) if (!operationIds.has(dependency)) diagnostics.push(operationDiagnostic(operation, "invalid-plan", `Operation depends on unknown operation "${dependency}"`));
|
|
191
|
+
}
|
|
192
|
+
return sortDiagnostics(diagnostics);
|
|
193
|
+
}
|
|
194
|
+
function diagnoseSafety(operation, options, diagnostics) {
|
|
195
|
+
if (operation.status === "approved" && operation.decision?.action === "allow") return;
|
|
196
|
+
if (options.allowUnsafe || operation.safety === "unknown" && options.allowUnknown || operation.safety === "unsupported" && options.allowUnsupported || operation.safety === "destructive" && options.allowDestructive || operation.safety === "review-required" && options.allowReviewRequired) return;
|
|
197
|
+
const code = operation.safety;
|
|
198
|
+
if (code === "unknown") diagnostics.push(operationDiagnostic(operation, "unknown", `Operation has ${code} safety and cannot be emitted without an explicit policy`));
|
|
199
|
+
else if (code === "unsupported" || code === "destructive" || code === "review-required") diagnostics.push(operationDiagnostic(operation, code, `Operation has ${code} safety and cannot be emitted without an explicit policy`));
|
|
200
|
+
}
|
|
201
|
+
function diagnoseExecutionContext(operation, options, diagnostics) {
|
|
202
|
+
if (operation.lock === "unknown") diagnostics.push(operationDiagnostic(operation, "lock-conflict", "Operation lock requirement is unknown"));
|
|
203
|
+
else if (options.lock !== void 0 && lockRank[operation.lock] > lockRank[options.lock]) diagnostics.push({
|
|
204
|
+
...operationDiagnostic(operation, "lock-conflict", `Operation requires a ${operation.lock} lock but the emission policy permits at most ${options.lock} lock`),
|
|
205
|
+
lock: operation.lock
|
|
206
|
+
});
|
|
207
|
+
if (operation.transaction === "unknown") diagnostics.push(operationDiagnostic(operation, "transaction-conflict", "Operation transaction requirement is unknown"));
|
|
208
|
+
else if (options.transaction === "autocommit" && operation.transaction === "required") diagnostics.push({
|
|
209
|
+
...operationDiagnostic(operation, "transaction-conflict", "Operation requires a transaction but the emission policy is autocommit"),
|
|
210
|
+
transaction: operation.transaction
|
|
211
|
+
});
|
|
212
|
+
else if (options.transaction === "none" && operation.transaction === "required") diagnostics.push({
|
|
213
|
+
...operationDiagnostic(operation, "transaction-conflict", "Operation requires a transaction but the emission policy disables transactions"),
|
|
214
|
+
transaction: operation.transaction
|
|
215
|
+
});
|
|
216
|
+
else if (options.transaction === "managed" && operation.transaction === "forbidden") diagnostics.push({
|
|
217
|
+
...operationDiagnostic(operation, "transaction-conflict", "Operation forbids a transaction but the emission policy manages one"),
|
|
218
|
+
transaction: operation.transaction
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function diagnoseVersion(operation, options, features, diagnostics) {
|
|
222
|
+
if (options.serverVersion === void 0) return;
|
|
223
|
+
const required = minimumVersion(features.dialect, operation);
|
|
224
|
+
if (required === void 0) return;
|
|
225
|
+
const actual = parseVersion(options.serverVersion);
|
|
226
|
+
if (actual === void 0 || compareVersion(actual, required) < 0) diagnostics.push({
|
|
227
|
+
...operationDiagnostic(operation, "server-version", `${features.dialect} ${operation.type} ${operation.kind} requires server version ${required.join(".")}`),
|
|
228
|
+
requiredVersion: required.join("."),
|
|
229
|
+
actualVersion: String(options.serverVersion)
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function minimumVersion(dialect, operation) {
|
|
233
|
+
if (dialect === "sqlite") {
|
|
234
|
+
if (operation.kind === "column" && operation.type === "remove") return [3, 35];
|
|
235
|
+
if (operation.kind === "column" && operation.type === "physical-rename") return [3, 25];
|
|
236
|
+
if (operation.kind === "column" && operation.type === "property-change") return [3, 35];
|
|
237
|
+
if (operation.kind === "table" && operation.type === "property-change") return [3, 35];
|
|
238
|
+
}
|
|
239
|
+
if (dialect === "mysql") {
|
|
240
|
+
if (operation.kind === "column" && operation.type === "physical-rename") return [8, 0];
|
|
241
|
+
if (operation.kind === "constraint" && operation.type === "add") {
|
|
242
|
+
const value = operation.origin?.after?.value;
|
|
243
|
+
if (isRecord(value) && value.kind === "check") return [
|
|
244
|
+
8,
|
|
245
|
+
0,
|
|
246
|
+
16
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (dialect === "postgresql" && operation.kind === "partition" && operation.type === "add") return [10];
|
|
251
|
+
}
|
|
252
|
+
function diagnoseOperation(operation, schemaDialect, features, diagnostics) {
|
|
253
|
+
const kind = operation.kind;
|
|
254
|
+
if (!isOperationSupported(features, operation)) diagnostics.push(operationDiagnostic(operation, operation.safety === "unknown" ? "unknown" : "unsupported", `The ${features.dialect} DDL emitter does not support ${kind} operations`));
|
|
255
|
+
const value = (operation.origin?.after ?? operation.origin?.before)?.value;
|
|
256
|
+
if (!isRecord(value)) return;
|
|
257
|
+
if (storageDialect(value.storage) !== void 0 && storageDialect(value.storage) !== features.dialect) diagnostics.push(operationDiagnostic(operation, "dialect-mismatch", `Storage declaration belongs to ${storageDialect(value.storage)} but the emitter is ${features.dialect}`));
|
|
258
|
+
for (const expression of expressionsIn(value)) {
|
|
259
|
+
if (expression.dialect !== void 0 && expression.dialect !== features.dialect) diagnostics.push(operationDiagnostic(operation, "dialect-mismatch", `Expression is tagged for ${expression.dialect} but the emitter is ${features.dialect}`));
|
|
260
|
+
if (expression.sql.includes("?")) diagnostics.push(operationDiagnostic(operation, "malformed-operation", "Schema expressions must be parameter-free"));
|
|
261
|
+
if (expression.expressionKind.toLowerCase().startsWith("json") && !schemaDialect.capabilities?.includes("json")) diagnostics.push(operationDiagnostic(operation, "capability", "This schema expression requires the dialect JSON capability"));
|
|
262
|
+
}
|
|
263
|
+
if (value.generatedColumn !== void 0 && !features.supports.has("generated-column")) diagnostics.push(operationDiagnostic(operation, "unsupported", "Generated columns are not supported by this DDL emitter"));
|
|
264
|
+
if (value.predicate !== void 0 && !features.supports.has("index-predicate")) diagnostics.push(operationDiagnostic(operation, "unsupported", "Index predicates are not supported by this DDL emitter"));
|
|
265
|
+
if (value.includedColumns !== void 0 && Array.isArray(value.includedColumns) && value.includedColumns.length > 0 && !features.supports.has("index-include")) diagnostics.push(operationDiagnostic(operation, "unsupported", "Included index columns are not supported by this DDL emitter"));
|
|
266
|
+
if (kind === "opaque-object" || kind === "deferred-object") diagnostics.push(operationDiagnostic(operation, "lossy", "Opaque and deferred catalog facts never become inferred SQL"));
|
|
267
|
+
}
|
|
268
|
+
function isOperationSupported(features, operation) {
|
|
269
|
+
if (!features.supports.has(operation.kind)) return false;
|
|
270
|
+
if (operation.kind === "table" && operation.type === "property-change") return false;
|
|
271
|
+
if (operation.kind === "ownership" && operation.type === "remove") return false;
|
|
272
|
+
if (operation.kind === "namespace" && features.dialect !== "postgresql") return false;
|
|
273
|
+
if (operation.kind === "comment" && features.dialect === "sqlite") return false;
|
|
274
|
+
if (operation.kind === "constraint" && features.dialect === "sqlite" && (operation.type === "add" || operation.type === "remove")) return false;
|
|
275
|
+
if (operation.kind === "materialized-view" && features.dialect !== "postgresql") return false;
|
|
276
|
+
if (operation.kind === "routine" && operation.type === "physical-rename") return false;
|
|
277
|
+
if (operation.kind === "view" && operation.type === "physical-rename" && features.dialect === "sqlite") return false;
|
|
278
|
+
if (operation.kind === "constraint" && operation.type === "physical-rename" && features.dialect !== "postgresql") return false;
|
|
279
|
+
if (operation.kind === "trigger" && operation.type === "physical-rename") return false;
|
|
280
|
+
if (operation.kind === "policy" && operation.type === "property-change") return false;
|
|
281
|
+
if (operation.kind === "extension" && operation.type === "property-change") return false;
|
|
282
|
+
if (operation.kind === "partition" && operation.type === "physical-rename") return false;
|
|
283
|
+
if (operation.kind === "ownership" && operation.type === "physical-rename") return false;
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
function renderOperation(operation, operations, dialect, features) {
|
|
287
|
+
if (operation.type === "custom-sql") return normalizeSql(operation.customSql?.sql ?? "");
|
|
288
|
+
const object = operation.type === "remove" ? operation.origin?.before : operation.origin?.after ?? operation.origin?.before;
|
|
289
|
+
if (object === void 0) return void 0;
|
|
290
|
+
const value = object.value;
|
|
291
|
+
switch (operation.type) {
|
|
292
|
+
case "add": return renderAdd(operation, value, operations, dialect, features);
|
|
293
|
+
case "remove": return renderRemove(operation, value, operations, dialect, features);
|
|
294
|
+
case "physical-rename": return renderRename(operation, operations, dialect, features);
|
|
295
|
+
case "property-change": return renderPropertyChange(operation, value, operations, dialect, features);
|
|
296
|
+
default: return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function renderAdd(operation, value, operations, dialect, features) {
|
|
300
|
+
if (!isRecord(value)) return void 0;
|
|
301
|
+
const name = requiredName(operation, value);
|
|
302
|
+
switch (operation.kind) {
|
|
303
|
+
case "namespace": return features.dialect === "postgresql" ? `CREATE SCHEMA ${dialect.quoteIdentifier(name)}` : void 0;
|
|
304
|
+
case "table": return renderCreateTable(operation, value, dialect, features);
|
|
305
|
+
case "column": return `ALTER TABLE ${parentTable(operation, operations, dialect)} ADD COLUMN ${renderColumn(value, dialect, features)}`;
|
|
306
|
+
case "constraint": return renderAddConstraint(operation, value, operations, dialect, features);
|
|
307
|
+
case "index": return renderCreateIndex(operation, value, operations, dialect, features);
|
|
308
|
+
case "view":
|
|
309
|
+
case "materialized-view": return renderCreateView(operation, value, dialect, features);
|
|
310
|
+
case "sequence": return renderCreateSequence(operation, value, dialect, features);
|
|
311
|
+
case "enum": return renderCreateEnum(operation, value, dialect, features);
|
|
312
|
+
case "domain": return renderCreateDomain(operation, value, dialect, features);
|
|
313
|
+
case "collation": return renderCreateCollation(operation, value, dialect, features);
|
|
314
|
+
case "trigger": return renderCreateTrigger(operation, value, operations, dialect, features);
|
|
315
|
+
case "routine": return renderCreateRoutine(operation, value, dialect, features);
|
|
316
|
+
case "partition": return renderCreatePartition(operation, value, operations, dialect, features);
|
|
317
|
+
case "policy": return renderCreatePolicy(operation, value, operations, dialect, features);
|
|
318
|
+
case "extension": return renderCreateExtension(operation, value, dialect, features);
|
|
319
|
+
case "comment": return renderComment(operation, value, dialect, features);
|
|
320
|
+
case "ownership": return renderOwnership(operation, value, dialect, features);
|
|
321
|
+
default: return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function renderRemove(operation, value, operations, dialect, features) {
|
|
325
|
+
if (!isRecord(value)) return void 0;
|
|
326
|
+
const name = requiredName(operation, value);
|
|
327
|
+
switch (operation.kind) {
|
|
328
|
+
case "namespace": return features.dialect === "postgresql" ? `DROP SCHEMA ${dialect.quoteIdentifier(name)}` : void 0;
|
|
329
|
+
case "table": return `DROP TABLE ${qualifiedName(operation, name, dialect)}`;
|
|
330
|
+
case "column": return `ALTER TABLE ${parentTable(operation, operations, dialect)} DROP COLUMN ${dialect.quoteIdentifier(name)}`;
|
|
331
|
+
case "constraint": return renderDropConstraint(operation, value, operations, dialect, features);
|
|
332
|
+
case "index": return renderDropIndex(operation, value, operations, dialect, features);
|
|
333
|
+
case "view": return `DROP VIEW ${qualifiedName(operation, name, dialect)}`;
|
|
334
|
+
case "materialized-view": return features.dialect === "postgresql" ? `DROP MATERIALIZED VIEW ${qualifiedName(operation, name, dialect)}` : void 0;
|
|
335
|
+
case "sequence": return features.dialect === "postgresql" ? `DROP SEQUENCE ${qualifiedName(operation, name, dialect)}` : void 0;
|
|
336
|
+
case "enum": return features.dialect === "postgresql" ? `DROP TYPE ${qualifiedName(operation, name, dialect)}` : void 0;
|
|
337
|
+
case "domain": return features.dialect === "postgresql" ? `DROP DOMAIN ${qualifiedName(operation, name, dialect)}` : void 0;
|
|
338
|
+
case "collation": return `DROP COLLATION ${qualifiedName(operation, name, dialect)}`;
|
|
339
|
+
case "trigger": return `DROP TRIGGER ${qualifiedName(operation, name, dialect)}`;
|
|
340
|
+
case "routine": return renderDropRoutine(operation, value, dialect, features);
|
|
341
|
+
case "partition": return renderDropPartition(operation, value, operations, dialect, features);
|
|
342
|
+
case "policy": return features.dialect === "postgresql" ? `DROP POLICY ${dialect.quoteIdentifier(name)} ON ${tableReference(operation, value.table, operations, dialect)}` : void 0;
|
|
343
|
+
case "extension": return features.dialect === "postgresql" ? `DROP EXTENSION ${dialect.quoteIdentifier(extensionName(value, name))}` : void 0;
|
|
344
|
+
case "comment": return renderComment(operation, {
|
|
345
|
+
...value,
|
|
346
|
+
text: null
|
|
347
|
+
}, dialect, features);
|
|
348
|
+
case "ownership": return;
|
|
349
|
+
default: return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function renderPropertyChange(operation, value, operations, dialect, features) {
|
|
353
|
+
if (!isRecord(value)) return void 0;
|
|
354
|
+
if (operation.kind === "comment" || operation.kind === "ownership") return operation.kind === "comment" ? renderComment(operation, value, dialect, features) : renderOwnership(operation, value, dialect, features);
|
|
355
|
+
if (operation.kind === "column") return renderAlterColumn(operation, value, operations, dialect, features);
|
|
356
|
+
if (operation.kind === "index") {
|
|
357
|
+
const before = operation.origin?.before?.value;
|
|
358
|
+
const dropped = before === void 0 ? void 0 : renderDropIndex(operation, before, operations, dialect, features);
|
|
359
|
+
const created = renderCreateIndex(operation, value, operations, dialect, features);
|
|
360
|
+
if (dropped === void 0) return created;
|
|
361
|
+
if (created === void 0) return dropped;
|
|
362
|
+
return `${dropped};\n${created}`;
|
|
363
|
+
}
|
|
364
|
+
if (operation.kind === "view" || operation.kind === "materialized-view") return renderReplaceView(operation, value, dialect, features);
|
|
365
|
+
if (operation.kind === "routine") {
|
|
366
|
+
const before = operation.origin?.before?.value;
|
|
367
|
+
const dropped = before === void 0 ? void 0 : renderDropRoutine(operation, before, dialect, features);
|
|
368
|
+
const created = renderCreateRoutine(operation, value, dialect, features);
|
|
369
|
+
if (dropped === void 0) return created;
|
|
370
|
+
if (created === void 0) return dropped;
|
|
371
|
+
return `${dropped};\n${created}`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function renderRename(operation, operations, dialect, features) {
|
|
375
|
+
const before = operation.origin?.before;
|
|
376
|
+
const after = operation.origin?.after;
|
|
377
|
+
if (before === void 0 || after === void 0) return void 0;
|
|
378
|
+
const oldName = requiredValueName(before.value);
|
|
379
|
+
const newName = requiredValueName(after.value);
|
|
380
|
+
const oldQualified = qualifiedName(operation, oldName, dialect);
|
|
381
|
+
const quotedNew = dialect.quoteIdentifier(newName);
|
|
382
|
+
switch (operation.kind) {
|
|
383
|
+
case "namespace": return features.dialect === "postgresql" ? `ALTER SCHEMA ${dialect.quoteIdentifier(oldName)} RENAME TO ${quotedNew}` : void 0;
|
|
384
|
+
case "table": return `ALTER TABLE ${oldQualified} RENAME TO ${quotedNew}`;
|
|
385
|
+
case "column": return `ALTER TABLE ${parentTable(operation, operations, dialect)} RENAME COLUMN ${dialect.quoteIdentifier(oldName)} TO ${quotedNew}`;
|
|
386
|
+
case "constraint": return features.dialect === "postgresql" ? `ALTER TABLE ${parentTable(operation, operations, dialect)} RENAME CONSTRAINT ${dialect.quoteIdentifier(oldName)} TO ${quotedNew}` : void 0;
|
|
387
|
+
case "index":
|
|
388
|
+
if (features.dialect === "postgresql") return `ALTER INDEX ${oldQualified} RENAME TO ${quotedNew}`;
|
|
389
|
+
if (features.dialect === "mysql") return `ALTER TABLE ${parentTable(operation, operations, dialect)} RENAME INDEX ${dialect.quoteIdentifier(oldName)} TO ${quotedNew}`;
|
|
390
|
+
return;
|
|
391
|
+
case "view": return features.dialect === "mysql" ? `RENAME TABLE ${oldQualified} TO ${qualifiedName(operation, newName, dialect)}` : `ALTER VIEW ${oldQualified} RENAME TO ${quotedNew}`;
|
|
392
|
+
case "materialized-view": return features.dialect === "postgresql" ? `ALTER MATERIALIZED VIEW ${oldQualified} RENAME TO ${quotedNew}` : void 0;
|
|
393
|
+
case "sequence": return features.dialect === "postgresql" ? `ALTER SEQUENCE ${oldQualified} RENAME TO ${quotedNew}` : void 0;
|
|
394
|
+
case "enum":
|
|
395
|
+
case "domain": return features.dialect === "postgresql" ? `ALTER TYPE ${oldQualified} RENAME TO ${quotedNew}` : void 0;
|
|
396
|
+
case "collation": return features.dialect === "postgresql" ? `ALTER COLLATION ${oldQualified} RENAME TO ${quotedNew}` : void 0;
|
|
397
|
+
default: return;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function renderCreateTable(operation, value, dialect, features) {
|
|
401
|
+
const definitions = arrayOfRecords(value.columns).map((column) => renderColumn(column, dialect, features));
|
|
402
|
+
return `CREATE TABLE ${qualifiedName(operation, requiredName(operation, value), dialect)} (${definitions.join(", ")})`;
|
|
403
|
+
}
|
|
404
|
+
function renderColumn(value, dialect, features) {
|
|
405
|
+
const name = stringValue(value.physicalName) ?? stringValue(value.id);
|
|
406
|
+
if (name === void 0) throw new TypeError("Column is missing a physical name");
|
|
407
|
+
const parts = [dialect.quoteIdentifier(name), renderStorage(value.storage, dialect)];
|
|
408
|
+
if (value.generatedColumn !== void 0) {
|
|
409
|
+
const generated = recordValue(value.generatedColumn);
|
|
410
|
+
if (generated === void 0) throw new TypeError("Generated column declaration is malformed");
|
|
411
|
+
const expression = expressionValue(generated.expression);
|
|
412
|
+
if (expression === void 0) throw new TypeError("Generated column is missing its expression");
|
|
413
|
+
parts.push(`GENERATED ALWAYS AS (${expression.sql}) ${generated.mode === "virtual" ? "VIRTUAL" : "STORED"}`);
|
|
414
|
+
}
|
|
415
|
+
if (value.nullable === false) parts.push("NOT NULL");
|
|
416
|
+
if (value.default !== void 0) {
|
|
417
|
+
const rendered = renderDefault(value.default, dialect);
|
|
418
|
+
parts.push(`DEFAULT ${rendered}`);
|
|
419
|
+
} else if (value.hasDefault === true) throw new TypeError("Column declares a default but no default value was retained");
|
|
420
|
+
const identity = recordValue(value.identity);
|
|
421
|
+
if (identity !== void 0) {
|
|
422
|
+
if (features.dialect === "postgresql") parts.push(`GENERATED ${identity.generation === "always" ? "ALWAYS" : "BY DEFAULT"} AS IDENTITY`);
|
|
423
|
+
else if (features.dialect === "mysql") parts.push("AUTO_INCREMENT");
|
|
424
|
+
else throw new TypeError("SQLite identity declarations are not supported by this emitter");
|
|
425
|
+
}
|
|
426
|
+
if (value.onUpdate !== void 0) {
|
|
427
|
+
const expression = expressionValue(value.onUpdate);
|
|
428
|
+
if (expression === void 0) throw new TypeError("Column onUpdate value is malformed");
|
|
429
|
+
if (features.dialect !== "mysql") throw new TypeError(`${features.dialect} does not support MySQL ON UPDATE column declarations`);
|
|
430
|
+
parts.push(`ON UPDATE ${expression.sql}`);
|
|
431
|
+
}
|
|
432
|
+
return parts.join(" ");
|
|
433
|
+
}
|
|
434
|
+
function renderStorage(value, dialect) {
|
|
435
|
+
const storage = recordValue(value);
|
|
436
|
+
if (storage === void 0) return "TEXT";
|
|
437
|
+
if (storage.kind === "native") {
|
|
438
|
+
const type = stringValue(storage.type);
|
|
439
|
+
if (type === void 0 || type.trim().length === 0) throw new TypeError("Native storage declaration must have a non-empty type");
|
|
440
|
+
return type;
|
|
441
|
+
}
|
|
442
|
+
if (storage.kind !== "portable") throw new TypeError("Column storage kind is invalid");
|
|
443
|
+
const type = stringValue(storage.type);
|
|
444
|
+
if (type === void 0) throw new TypeError("Portable storage declaration is missing its type");
|
|
445
|
+
const key = type.toLowerCase();
|
|
446
|
+
const rendered = (dialect.name === "postgresql" ? {
|
|
447
|
+
integer: "INTEGER",
|
|
448
|
+
numeric: "NUMERIC",
|
|
449
|
+
text: "TEXT",
|
|
450
|
+
boolean: "BOOLEAN",
|
|
451
|
+
date: "DATE",
|
|
452
|
+
timestamp: "TIMESTAMP",
|
|
453
|
+
uuid: "UUID",
|
|
454
|
+
json: "JSONB",
|
|
455
|
+
bigint: "BIGINT",
|
|
456
|
+
binary: "BYTEA"
|
|
457
|
+
} : dialect.name === "mysql" ? {
|
|
458
|
+
integer: "INT",
|
|
459
|
+
numeric: "DECIMAL",
|
|
460
|
+
text: "TEXT",
|
|
461
|
+
boolean: "BOOLEAN",
|
|
462
|
+
date: "DATE",
|
|
463
|
+
timestamp: "DATETIME",
|
|
464
|
+
uuid: "CHAR(36)",
|
|
465
|
+
json: "JSON",
|
|
466
|
+
bigint: "BIGINT",
|
|
467
|
+
binary: "VARBINARY"
|
|
468
|
+
} : {
|
|
469
|
+
integer: "INTEGER",
|
|
470
|
+
numeric: "NUMERIC",
|
|
471
|
+
text: "TEXT",
|
|
472
|
+
boolean: "INTEGER",
|
|
473
|
+
date: "TEXT",
|
|
474
|
+
timestamp: "TEXT",
|
|
475
|
+
uuid: "TEXT",
|
|
476
|
+
json: "TEXT",
|
|
477
|
+
bigint: "INTEGER",
|
|
478
|
+
binary: "BLOB"
|
|
479
|
+
})[key];
|
|
480
|
+
if (rendered === void 0) throw new TypeError(`Unsupported portable storage type "${type}" for ${dialect.name}`);
|
|
481
|
+
return rendered;
|
|
482
|
+
}
|
|
483
|
+
function renderDefault(value, dialect) {
|
|
484
|
+
const descriptor = recordValue(value);
|
|
485
|
+
if (descriptor === void 0) throw new TypeError("Default declaration is malformed");
|
|
486
|
+
if (descriptor.kind === "external") throw new TypeError("External defaults are lossy and cannot be rendered");
|
|
487
|
+
if (descriptor.kind === "expression") {
|
|
488
|
+
const expression = expressionValue(descriptor.expression);
|
|
489
|
+
if (expression === void 0) throw new TypeError("Default expression is malformed");
|
|
490
|
+
return expression.sql;
|
|
491
|
+
}
|
|
492
|
+
if (descriptor.kind !== "literal") throw new TypeError("Default declaration kind is invalid");
|
|
493
|
+
return renderLiteral(descriptor.value, dialect);
|
|
494
|
+
}
|
|
495
|
+
function renderLiteral(value, dialect) {
|
|
496
|
+
const literal = recordValue(value);
|
|
497
|
+
if (literal === void 0) throw new TypeError("Literal declaration is malformed");
|
|
498
|
+
switch (literal.kind) {
|
|
499
|
+
case "null": return "NULL";
|
|
500
|
+
case "boolean": return schemaLiteral(dialect, literal.value === true);
|
|
501
|
+
case "string":
|
|
502
|
+
if (typeof literal.value !== "string") throw new TypeError("String literal value is malformed");
|
|
503
|
+
return schemaLiteral(dialect, literal.value);
|
|
504
|
+
case "number":
|
|
505
|
+
case "bigint":
|
|
506
|
+
if (typeof literal.value !== "string" || !/^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/u.test(literal.value)) throw new TypeError("Numeric literal value is not canonical");
|
|
507
|
+
return literal.value;
|
|
508
|
+
default: throw new TypeError("Literal kind is invalid");
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function schemaLiteral(dialect, value) {
|
|
512
|
+
const rendered = dialect.renderSchemaLiteral?.(value);
|
|
513
|
+
if (rendered !== void 0) {
|
|
514
|
+
if (rendered.includes("?")) throw new TypeError("Schema literal renderer returned a parameter placeholder");
|
|
515
|
+
return rendered;
|
|
516
|
+
}
|
|
517
|
+
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
|
518
|
+
if (typeof value === "string") return `'${value.replaceAll("'", "''")}'`;
|
|
519
|
+
if (value === null) return "NULL";
|
|
520
|
+
throw new TypeError("Unsupported schema literal");
|
|
521
|
+
}
|
|
522
|
+
function renderAddConstraint(operation, value, operations, dialect, features) {
|
|
523
|
+
const table = parentTable(operation, operations, dialect);
|
|
524
|
+
const name = requiredName(operation, value);
|
|
525
|
+
const kind = stringValue(value.kind);
|
|
526
|
+
const columns = stringArray(value.columns);
|
|
527
|
+
const terms = columns.map((column) => dialect.quoteIdentifier(column)).join(", ");
|
|
528
|
+
let body;
|
|
529
|
+
if (kind === "primary-key") body = `PRIMARY KEY (${terms})`;
|
|
530
|
+
else if (kind === "unique" || kind === "unique-constraint") body = `UNIQUE (${terms})`;
|
|
531
|
+
else if (kind === "foreign-key") {
|
|
532
|
+
const target = recordValue(value.target);
|
|
533
|
+
const targetTable = target === void 0 ? void 0 : recordValue(target.table);
|
|
534
|
+
const targetName = targetTable === void 0 ? void 0 : stringValue(targetTable.id) ?? stringValue(targetTable.physicalName);
|
|
535
|
+
const targetColumns = target === void 0 ? [] : stringArray(target.columns);
|
|
536
|
+
if (targetName === void 0 || columns.length === 0 || targetColumns.length === 0) throw new TypeError("Foreign-key constraint is missing target identity");
|
|
537
|
+
body = `FOREIGN KEY (${terms}) REFERENCES ${qualifiedName(operation, targetName, dialect)} (${targetColumns.map((column) => dialect.quoteIdentifier(column)).join(", ")})`;
|
|
538
|
+
const onDelete = stringValue(value.onDelete);
|
|
539
|
+
const onUpdate = stringValue(value.onUpdate);
|
|
540
|
+
if (onDelete !== void 0 && onDelete !== "no-action") body += ` ON DELETE ${sqlAction(onDelete)}`;
|
|
541
|
+
if (onUpdate !== void 0 && onUpdate !== "no-action") body += ` ON UPDATE ${sqlAction(onUpdate)}`;
|
|
542
|
+
} else if (kind === "check") {
|
|
543
|
+
const expression = expressionValue(value.expression);
|
|
544
|
+
if (expression === void 0) throw new TypeError("Check constraint is missing its expression");
|
|
545
|
+
body = `CHECK (${expression.sql})`;
|
|
546
|
+
} else throw new TypeError(`Unsupported constraint kind "${String(kind)}"`);
|
|
547
|
+
if (value.deferrable === true) {
|
|
548
|
+
if (features.dialect === "postgresql") {
|
|
549
|
+
body += " DEFERRABLE";
|
|
550
|
+
if (value.initially === "deferred") body += " INITIALLY DEFERRED";
|
|
551
|
+
else if (value.initially === "immediate") body += " INITIALLY IMMEDIATE";
|
|
552
|
+
} else throw new TypeError(`${features.dialect} does not support deferrable constraints`);
|
|
553
|
+
}
|
|
554
|
+
return `ALTER TABLE ${table} ADD CONSTRAINT ${dialect.quoteIdentifier(name)} ${body}`;
|
|
555
|
+
}
|
|
556
|
+
function renderDropConstraint(operation, value, operations, dialect, features) {
|
|
557
|
+
const table = parentTable(operation, operations, dialect);
|
|
558
|
+
const name = requiredName(operation, value);
|
|
559
|
+
const kind = stringValue(value.kind);
|
|
560
|
+
if (features.dialect === "postgresql") return `ALTER TABLE ${table} DROP CONSTRAINT ${dialect.quoteIdentifier(name)}`;
|
|
561
|
+
if (features.dialect === "mysql") {
|
|
562
|
+
if (kind === "primary-key") return `ALTER TABLE ${table} DROP PRIMARY KEY`;
|
|
563
|
+
if (kind === "foreign-key") return `ALTER TABLE ${table} DROP FOREIGN KEY ${dialect.quoteIdentifier(name)}`;
|
|
564
|
+
return `ALTER TABLE ${table} DROP INDEX ${dialect.quoteIdentifier(name)}`;
|
|
565
|
+
}
|
|
566
|
+
throw new TypeError("SQLite cannot drop table constraints without rebuilding the table");
|
|
567
|
+
}
|
|
568
|
+
function renderCreateIndex(operation, value, operations, dialect, features) {
|
|
569
|
+
const name = requiredName(operation, value);
|
|
570
|
+
const terms = arrayOfRecords(value.terms).sort((left, right) => numberValue(left.position) - numberValue(right.position)).map((term) => renderIndexTerm(term, dialect)).join(", ");
|
|
571
|
+
if (terms.length === 0) throw new TypeError("Index must contain at least one term");
|
|
572
|
+
const table = parentTable(operation, operations, dialect);
|
|
573
|
+
let sql = `CREATE ${value.unique === true ? "UNIQUE " : ""}INDEX ${qualifiedName(operation, name, dialect)} ON ${table}`;
|
|
574
|
+
const method = recordValue(value.dialect)?.method;
|
|
575
|
+
if (features.dialect === "postgresql" && typeof method === "string") sql += ` USING ${method}`;
|
|
576
|
+
sql += ` (${terms})`;
|
|
577
|
+
const included = stringArray(value.includedColumns);
|
|
578
|
+
if (included.length > 0) sql += ` INCLUDE (${included.map((column) => dialect.quoteIdentifier(column)).join(", ")})`;
|
|
579
|
+
const predicate = expressionValue(value.predicate);
|
|
580
|
+
if (predicate !== void 0) sql += ` WHERE ${predicate.sql}`;
|
|
581
|
+
return sql;
|
|
582
|
+
}
|
|
583
|
+
function renderIndexTerm(value, dialect) {
|
|
584
|
+
const kind = stringValue(value.kind);
|
|
585
|
+
let sql;
|
|
586
|
+
if (kind === "column") {
|
|
587
|
+
const column = stringValue(value.column);
|
|
588
|
+
if (column === void 0) throw new TypeError("Index column term is missing its column");
|
|
589
|
+
sql = dialect.quoteIdentifier(column);
|
|
590
|
+
} else if (kind === "expression") {
|
|
591
|
+
const expression = expressionValue(value.expression);
|
|
592
|
+
if (expression === void 0) throw new TypeError("Index expression term is malformed");
|
|
593
|
+
sql = `(${expression.sql})`;
|
|
594
|
+
} else throw new TypeError(`Unsupported index term kind "${String(kind)}"`);
|
|
595
|
+
const direction = stringValue(value.direction);
|
|
596
|
+
if (direction === "ASC" || direction === "DESC") sql += ` ${direction}`;
|
|
597
|
+
const nulls = stringValue(value.nulls);
|
|
598
|
+
if (nulls === "FIRST" || nulls === "LAST") sql += ` NULLS ${nulls}`;
|
|
599
|
+
return sql;
|
|
600
|
+
}
|
|
601
|
+
function renderDropIndex(operation, value, operations, dialect, features) {
|
|
602
|
+
const name = requiredName(operation, value);
|
|
603
|
+
if (features.dialect === "mysql") return `ALTER TABLE ${parentTable(operation, operations, dialect)} DROP INDEX ${dialect.quoteIdentifier(name)}`;
|
|
604
|
+
return `DROP INDEX ${qualifiedName(operation, name, dialect)}`;
|
|
605
|
+
}
|
|
606
|
+
function renderCreateView(operation, value, dialect, features) {
|
|
607
|
+
const name = requiredName(operation, value);
|
|
608
|
+
const definition = expressionValue(value.definition);
|
|
609
|
+
if (definition === void 0) throw new TypeError("View is missing its definition");
|
|
610
|
+
if (operation.kind === "materialized-view" && features.dialect !== "postgresql") throw new TypeError(`${features.dialect} does not support materialized views`);
|
|
611
|
+
let sql = `CREATE ${operation.kind === "materialized-view" ? "MATERIALIZED " : ""}VIEW ${qualifiedName(operation, name, dialect)}`;
|
|
612
|
+
const columns = arrayOfRecords(value.columns);
|
|
613
|
+
if (columns.length > 0) sql += ` (${columns.map((column) => dialect.quoteIdentifier(requiredValueName(column))).join(", ")})`;
|
|
614
|
+
sql += ` AS ${definition.sql}`;
|
|
615
|
+
const checkOption = stringValue(value.checkOption);
|
|
616
|
+
if (checkOption === "local") sql += " WITH LOCAL CHECK OPTION";
|
|
617
|
+
else if (checkOption === "cascaded") sql += " WITH CASCADED CHECK OPTION";
|
|
618
|
+
return sql;
|
|
619
|
+
}
|
|
620
|
+
function renderReplaceView(operation, value, dialect, features) {
|
|
621
|
+
const name = requiredName(operation, value);
|
|
622
|
+
const definition = expressionValue(value.definition);
|
|
623
|
+
if (definition === void 0) throw new TypeError("View is missing its definition");
|
|
624
|
+
if (operation.kind === "materialized-view") {
|
|
625
|
+
if (features.dialect !== "postgresql") throw new TypeError(`${features.dialect} cannot replace materialized views`);
|
|
626
|
+
return `DROP MATERIALIZED VIEW ${qualifiedName(operation, name, dialect)};\n${renderCreateView(operation, value, dialect, features)}`;
|
|
627
|
+
}
|
|
628
|
+
if (features.dialect === "postgresql" || features.dialect === "mysql") return `CREATE OR REPLACE VIEW ${qualifiedName(operation, name, dialect)} AS ${definition.sql}`;
|
|
629
|
+
throw new TypeError("SQLite cannot alter a view definition without an explicit rebuild");
|
|
630
|
+
}
|
|
631
|
+
function renderCreateSequence(operation, value, dialect, features) {
|
|
632
|
+
if (features.dialect !== "postgresql") throw new TypeError(`${features.dialect} does not support sequences`);
|
|
633
|
+
const parts = [`CREATE SEQUENCE ${qualifiedName(operation, requiredName(operation, value), dialect)}`];
|
|
634
|
+
for (const [key, keyword] of [
|
|
635
|
+
["start", "START WITH"],
|
|
636
|
+
["increment", "INCREMENT BY"],
|
|
637
|
+
["minimum", "MINVALUE"],
|
|
638
|
+
["maximum", "MAXVALUE"],
|
|
639
|
+
["cache", "CACHE"]
|
|
640
|
+
]) {
|
|
641
|
+
const fact = recordValue(value[key]);
|
|
642
|
+
if (fact === void 0) continue;
|
|
643
|
+
parts.push(`${keyword} ${renderValueFact(fact, dialect)}`);
|
|
644
|
+
}
|
|
645
|
+
if (value.cycle === true) parts.push("CYCLE");
|
|
646
|
+
else if (value.cycle === false) parts.push("NO CYCLE");
|
|
647
|
+
return parts.join(" ");
|
|
648
|
+
}
|
|
649
|
+
function renderCreateEnum(operation, value, dialect, features) {
|
|
650
|
+
if (features.dialect !== "postgresql") throw new TypeError(`${features.dialect} does not support standalone enum types`);
|
|
651
|
+
const name = requiredName(operation, value);
|
|
652
|
+
const values = arrayOfRecords(value.values).sort((left, right) => numberValue(left.ordinalPosition) - numberValue(right.ordinalPosition));
|
|
653
|
+
if (values.length === 0) throw new TypeError("Enum must contain at least one value");
|
|
654
|
+
return `CREATE TYPE ${qualifiedName(operation, name, dialect)} AS ENUM (${values.map((item) => schemaLiteral(dialect, item.value)).join(", ")})`;
|
|
655
|
+
}
|
|
656
|
+
function renderCreateDomain(operation, value, dialect, features) {
|
|
657
|
+
if (features.dialect !== "postgresql") throw new TypeError(`${features.dialect} does not support domains`);
|
|
658
|
+
let sql = `CREATE DOMAIN ${qualifiedName(operation, requiredName(operation, value), dialect)} AS ${renderStorage(value.storage, dialect)}`;
|
|
659
|
+
if (value.nullable === false) sql += " NOT NULL";
|
|
660
|
+
if (value.default !== void 0) sql += ` DEFAULT ${renderValueFact(recordValue(value.default) ?? {}, dialect)}`;
|
|
661
|
+
return sql;
|
|
662
|
+
}
|
|
663
|
+
function renderCreateCollation(operation, value, dialect, features) {
|
|
664
|
+
const name = requiredName(operation, value);
|
|
665
|
+
if (features.dialect === "sqlite") throw new TypeError("SQLite does not support named collations in DDL");
|
|
666
|
+
const locale = stringValue(value.locale);
|
|
667
|
+
const provider = stringValue(value.provider);
|
|
668
|
+
if (features.dialect === "postgresql") {
|
|
669
|
+
if (locale === void 0) throw new TypeError("PostgreSQL collation is missing its locale");
|
|
670
|
+
return `CREATE COLLATION ${qualifiedName(operation, name, dialect)} (LOCALE = ${schemaLiteral(dialect, locale)}${provider === void 0 ? "" : `, PROVIDER = ${schemaLiteral(dialect, provider)}`})`;
|
|
671
|
+
}
|
|
672
|
+
if (locale === void 0) throw new TypeError("MySQL collation is missing its locale");
|
|
673
|
+
throw new TypeError("MySQL collations are server metadata and cannot be created as named objects");
|
|
674
|
+
}
|
|
675
|
+
function renderCreateTrigger(operation, value, operations, dialect, features) {
|
|
676
|
+
const name = requiredName(operation, value);
|
|
677
|
+
const table = tableReference(operation, value.table, operations, dialect);
|
|
678
|
+
const timing = stringValue(value.timing);
|
|
679
|
+
const events = stringArray(value.events);
|
|
680
|
+
const body = expressionValue(value.body);
|
|
681
|
+
if (timing === void 0 || body === void 0 || events.length === 0) throw new TypeError("Trigger is missing timing, events, or body");
|
|
682
|
+
if (timing === "unknown") throw new TypeError("Trigger timing is unknown");
|
|
683
|
+
const supportedEvents = events.map((event) => event.toUpperCase()).join(" OR ");
|
|
684
|
+
const orientation = stringValue(value.orientation);
|
|
685
|
+
if (features.dialect === "mysql" && orientation === "statement") throw new TypeError("MySQL triggers only support row orientation");
|
|
686
|
+
let sql = `CREATE TRIGGER ${qualifiedName(operation, name, dialect)} ${timing.toUpperCase()} ${supportedEvents} ON ${table} FOR EACH ${orientation === "statement" ? "STATEMENT" : "ROW"} ${body.sql}`;
|
|
687
|
+
const condition = expressionValue(value.condition);
|
|
688
|
+
if (condition !== void 0) sql += ` WHEN (${condition.sql})`;
|
|
689
|
+
return sql;
|
|
690
|
+
}
|
|
691
|
+
function renderCreateRoutine(operation, value, dialect, features) {
|
|
692
|
+
if (features.dialect === "sqlite") throw new TypeError("SQLite does not support stored routines");
|
|
693
|
+
const name = requiredName(operation, value);
|
|
694
|
+
const kind = stringValue(value.routineKind);
|
|
695
|
+
if (kind !== "function" && kind !== "procedure") throw new TypeError(`Routine kind "${String(kind)}" is not supported by this emitter`);
|
|
696
|
+
const parameters = arrayOfRecords(value.parameters).sort((left, right) => numberValue(left.ordinalPosition) - numberValue(right.ordinalPosition)).map((parameter) => renderRoutineParameter(parameter, dialect)).join(", ");
|
|
697
|
+
const body = expressionValue(value.body);
|
|
698
|
+
if (body === void 0) throw new TypeError("Routine body is missing");
|
|
699
|
+
let sql = `CREATE ${kind.toUpperCase()} ${qualifiedName(operation, name, dialect)}(${parameters})`;
|
|
700
|
+
if (kind === "function") {
|
|
701
|
+
const returnType = renderStorage(value.returnType, dialect);
|
|
702
|
+
sql += ` RETURNS ${returnType}`;
|
|
703
|
+
}
|
|
704
|
+
const language = stringValue(value.language);
|
|
705
|
+
if (features.dialect === "postgresql") sql += ` LANGUAGE ${dialect.quoteIdentifier(language ?? "sql")} AS ${dollarQuote(body.sql)}`;
|
|
706
|
+
else sql += ` ${body.sql}`;
|
|
707
|
+
return sql;
|
|
708
|
+
}
|
|
709
|
+
function renderRoutineParameter(value, dialect) {
|
|
710
|
+
const name = stringValue(value.name);
|
|
711
|
+
const mode = stringValue(value.mode);
|
|
712
|
+
const storage = renderStorage(value.storage, dialect);
|
|
713
|
+
return `${mode === void 0 || mode === "in" ? "" : `${mode.toUpperCase()} `}${name === void 0 ? "" : `${dialect.quoteIdentifier(name)} `}${storage}`.trim();
|
|
714
|
+
}
|
|
715
|
+
function renderDropRoutine(operation, value, dialect, features) {
|
|
716
|
+
if (features.dialect === "sqlite") throw new TypeError("SQLite does not support stored routines");
|
|
717
|
+
const kind = stringValue(value.routineKind);
|
|
718
|
+
if (kind !== "function" && kind !== "procedure") throw new TypeError("Routine kind is unknown");
|
|
719
|
+
return `DROP ${kind.toUpperCase()} ${qualifiedName(operation, requiredName(operation, value), dialect)}`;
|
|
720
|
+
}
|
|
721
|
+
function renderCreatePartition(operation, value, operations, dialect, features) {
|
|
722
|
+
const name = requiredName(operation, value);
|
|
723
|
+
if (recordValue(value.parent) === void 0) throw new TypeError("Partition is missing its parent table");
|
|
724
|
+
const strategy = stringValue(value.strategy);
|
|
725
|
+
const bound = expressionValue(value.bound);
|
|
726
|
+
if (features.dialect === "postgresql") {
|
|
727
|
+
if (strategy === void 0 || strategy === "unknown" || bound === void 0) throw new TypeError("PostgreSQL partition requires strategy and bound");
|
|
728
|
+
return `CREATE TABLE ${qualifiedName(operation, name, dialect)} PARTITION OF ${tableReference(operation, value.parent, operations, dialect)} FOR VALUES ${bound.sql}`;
|
|
729
|
+
}
|
|
730
|
+
if (features.dialect === "mysql") {
|
|
731
|
+
if (bound === void 0) throw new TypeError("MySQL partition requires a bound expression");
|
|
732
|
+
return `ALTER TABLE ${tableReference(operation, value.parent, operations, dialect)} ADD PARTITION (PARTITION ${dialect.quoteIdentifier(name)} VALUES ${bound.sql})`;
|
|
733
|
+
}
|
|
734
|
+
throw new TypeError("SQLite does not support table partitions");
|
|
735
|
+
}
|
|
736
|
+
function renderDropPartition(operation, value, operations, dialect, features) {
|
|
737
|
+
const name = requiredName(operation, value);
|
|
738
|
+
if (features.dialect === "postgresql") return `DROP TABLE ${qualifiedName(operation, name, dialect)}`;
|
|
739
|
+
if (features.dialect === "mysql") return `ALTER TABLE ${tableReference(operation, value.parent, operations, dialect)} DROP PARTITION ${dialect.quoteIdentifier(name)}`;
|
|
740
|
+
throw new TypeError("SQLite does not support table partitions");
|
|
741
|
+
}
|
|
742
|
+
function renderCreatePolicy(operation, value, operations, dialect, features) {
|
|
743
|
+
if (features.dialect !== "postgresql") throw new TypeError(`${features.dialect} does not support row-level policies`);
|
|
744
|
+
const name = requiredName(operation, value);
|
|
745
|
+
const command = stringValue(value.command);
|
|
746
|
+
if (command === void 0 || command === "unknown") throw new TypeError("Policy command is unknown");
|
|
747
|
+
let sql = `CREATE POLICY ${dialect.quoteIdentifier(name)} ON ${tableReference(operation, value.table, operations, dialect)}`;
|
|
748
|
+
if (command !== "all") sql += ` FOR ${command.toUpperCase()}`;
|
|
749
|
+
const roles = stringArray(value.roles);
|
|
750
|
+
if (roles.length > 0) sql += ` TO ${roles.map((role) => dialect.quoteIdentifier(role)).join(", ")}`;
|
|
751
|
+
const using = expressionValue(value.using);
|
|
752
|
+
const check = expressionValue(value.check);
|
|
753
|
+
if (using !== void 0) sql += ` USING (${using.sql})`;
|
|
754
|
+
if (check !== void 0) sql += ` WITH CHECK (${check.sql})`;
|
|
755
|
+
return sql;
|
|
756
|
+
}
|
|
757
|
+
function renderCreateExtension(operation, value, dialect, features) {
|
|
758
|
+
if (features.dialect !== "postgresql") throw new TypeError(`${features.dialect} does not support PostgreSQL extensions`);
|
|
759
|
+
const name = extensionName(value, requiredName(operation, value));
|
|
760
|
+
let sql = `CREATE EXTENSION ${dialect.quoteIdentifier(name)}`;
|
|
761
|
+
const version = stringValue(value.extensionVersion);
|
|
762
|
+
const schema = stringValue(value.schema);
|
|
763
|
+
if (version !== void 0) sql += ` VERSION ${schemaLiteral(dialect, version)}`;
|
|
764
|
+
if (schema !== void 0) sql += ` SCHEMA ${dialect.quoteIdentifier(schema)}`;
|
|
765
|
+
return sql;
|
|
766
|
+
}
|
|
767
|
+
function renderComment(operation, value, dialect, features) {
|
|
768
|
+
const text = value.text === null ? null : stringValue(value.text);
|
|
769
|
+
const target = recordValue(value.object);
|
|
770
|
+
const targetKind = target === void 0 ? void 0 : stringValue(target.kind);
|
|
771
|
+
const targetName = target === void 0 ? void 0 : stringValue(target.id) ?? stringValue(target.physicalName);
|
|
772
|
+
const name = stringValue(value.physicalName) ?? targetName;
|
|
773
|
+
if (features.dialect === "postgresql") {
|
|
774
|
+
const type = commentTargetType(targetKind);
|
|
775
|
+
if (type === void 0 || name === void 0) throw new TypeError("Comment target is incomplete");
|
|
776
|
+
return `COMMENT ON ${type} ${qualifiedName(operation, name, dialect)} IS ${text === null ? "NULL" : schemaLiteral(dialect, text)}`;
|
|
777
|
+
}
|
|
778
|
+
if (features.dialect === "mysql") {
|
|
779
|
+
if (name === void 0) throw new TypeError("Comment target is incomplete");
|
|
780
|
+
if (targetKind === "column") {
|
|
781
|
+
if (text === null) throw new TypeError("MySQL column comments require a full column definition");
|
|
782
|
+
throw new TypeError("MySQL column comments require an explicit full ALTER TABLE definition");
|
|
783
|
+
}
|
|
784
|
+
return `ALTER TABLE ${qualifiedName(operation, name, dialect)} COMMENT = ${text === null ? schemaLiteral(dialect, "") : schemaLiteral(dialect, text)}`;
|
|
785
|
+
}
|
|
786
|
+
throw new TypeError("SQLite does not support persistent object comments");
|
|
787
|
+
}
|
|
788
|
+
function renderOwnership(operation, value, dialect, features) {
|
|
789
|
+
if (features.dialect !== "postgresql") throw new TypeError(`${features.dialect} does not support object ownership DDL`);
|
|
790
|
+
const owner = stringValue(value.owner);
|
|
791
|
+
const target = recordValue(value.object);
|
|
792
|
+
const targetKind = target === void 0 ? void 0 : stringValue(target.kind);
|
|
793
|
+
const targetName = target === void 0 ? void 0 : stringValue(target.id) ?? stringValue(target.physicalName);
|
|
794
|
+
const type = commentTargetType(targetKind);
|
|
795
|
+
if (owner === void 0 || targetName === void 0 || type === void 0) throw new TypeError("Ownership record is incomplete");
|
|
796
|
+
return `ALTER ${type} ${qualifiedName(operation, targetName, dialect)} OWNER TO ${dialect.quoteIdentifier(owner)}`;
|
|
797
|
+
}
|
|
798
|
+
function renderAlterColumn(operation, value, operations, dialect, features) {
|
|
799
|
+
const name = requiredName(operation, value);
|
|
800
|
+
const table = parentTable(operation, operations, dialect);
|
|
801
|
+
if (features.dialect === "sqlite") throw new TypeError("SQLite cannot alter column definitions without rebuilding the table");
|
|
802
|
+
if (features.dialect === "mysql") return `ALTER TABLE ${table} MODIFY COLUMN ${renderColumn(value, dialect, features)}`;
|
|
803
|
+
const before = operation.origin?.before?.value;
|
|
804
|
+
if (!isRecord(before)) throw new TypeError("Column property change is missing its prior value");
|
|
805
|
+
const statements = [];
|
|
806
|
+
if (!sameValue(before.storage, value.storage)) statements.push(`ALTER TABLE ${table} ALTER COLUMN ${dialect.quoteIdentifier(name)} TYPE ${renderStorage(value.storage, dialect)}`);
|
|
807
|
+
if (before.nullable !== value.nullable) statements.push(`ALTER TABLE ${table} ALTER COLUMN ${dialect.quoteIdentifier(name)} ${value.nullable === false ? "SET NOT NULL" : "DROP NOT NULL"}`);
|
|
808
|
+
if (!sameValue(before.default, value.default) || before.hasDefault !== value.hasDefault) {
|
|
809
|
+
if (value.default === void 0) statements.push(`ALTER TABLE ${table} ALTER COLUMN ${dialect.quoteIdentifier(name)} DROP DEFAULT`);
|
|
810
|
+
else statements.push(`ALTER TABLE ${table} ALTER COLUMN ${dialect.quoteIdentifier(name)} SET DEFAULT ${renderDefault(value.default, dialect)}`);
|
|
811
|
+
}
|
|
812
|
+
if (before.generatedColumn !== value.generatedColumn || before.identity !== value.identity) throw new TypeError("PostgreSQL generated and identity column changes require an explicit custom SQL operation");
|
|
813
|
+
return statements.join(";\n");
|
|
814
|
+
}
|
|
815
|
+
function parentTable(operation, operations, dialect) {
|
|
816
|
+
const parent = operation.origin?.after?.parent ?? operation.origin?.before?.parent;
|
|
817
|
+
const name = (parent === void 0 ? void 0 : operations.find((item) => item.kind === "table" && item.logicalId === parent.id && item.namespace === operation.namespace && item.status !== "skipped"))?.physicalName ?? parent?.id;
|
|
818
|
+
if (name === void 0 || name.length === 0) throw new TypeError("Child operation is missing its parent table identity");
|
|
819
|
+
return qualifiedName(operation, name, dialect);
|
|
820
|
+
}
|
|
821
|
+
function tableReference(operation, reference, operations, dialect) {
|
|
822
|
+
const value = recordValue(reference);
|
|
823
|
+
const id = value === void 0 ? void 0 : stringValue(value.id);
|
|
824
|
+
const physicalName = value === void 0 ? void 0 : stringValue(value.physicalName);
|
|
825
|
+
const name = (id === void 0 ? void 0 : operations.find((item) => item.kind === "table" && item.logicalId === id && item.namespace === operation.namespace && item.status !== "skipped"))?.physicalName ?? physicalName ?? id;
|
|
826
|
+
if (name === void 0 || name.length === 0) throw new TypeError("Referenced table identity is incomplete");
|
|
827
|
+
return qualifiedName(operation, name, dialect);
|
|
828
|
+
}
|
|
829
|
+
function qualifiedName(operation, name, dialect) {
|
|
830
|
+
const namespace = operation.namespace;
|
|
831
|
+
return namespace === void 0 || namespace.length === 0 ? dialect.quoteIdentifier(name) : `${dialect.quoteIdentifier(namespace)}.${dialect.quoteIdentifier(name)}`;
|
|
832
|
+
}
|
|
833
|
+
function requiredName(operation, value) {
|
|
834
|
+
const name = operation.physicalName ?? (isRecord(value) ? stringValue(value.physicalName) : void 0) ?? (isRecord(value) ? stringValue(value.id) : void 0);
|
|
835
|
+
if (name === void 0 || name.length === 0) throw new TypeError(`Operation "${operation.id}" is missing a physical name`);
|
|
836
|
+
return name;
|
|
837
|
+
}
|
|
838
|
+
function requiredValueName(value) {
|
|
839
|
+
const name = isRecord(value) ? stringValue(value.physicalName) ?? stringValue(value.id) : void 0;
|
|
840
|
+
if (name === void 0 || name.length === 0) throw new TypeError("Operation object is missing a physical name");
|
|
841
|
+
return name;
|
|
842
|
+
}
|
|
843
|
+
function operationName(operation, value) {
|
|
844
|
+
if (operation.kind === "namespace" && isRecord(value)) return stringValue(value.name);
|
|
845
|
+
if (!isRecord(value)) return void 0;
|
|
846
|
+
return operation.physicalName ?? stringValue(value.physicalName) ?? stringValue(value.id);
|
|
847
|
+
}
|
|
848
|
+
function requiresPhysicalName(kind) {
|
|
849
|
+
return kind !== "custom-sql";
|
|
850
|
+
}
|
|
851
|
+
function isCoveredByParent(operation, operations) {
|
|
852
|
+
if (operation.type === "add" && operation.kind !== "column") return false;
|
|
853
|
+
if (operation.type !== "add" && operation.type !== "remove") return false;
|
|
854
|
+
const parent = operation.origin?.after?.parent ?? operation.origin?.before?.parent;
|
|
855
|
+
if (parent === void 0) return false;
|
|
856
|
+
return operations.find((item) => (item.kind === "table" || item.kind === "view" || item.kind === "materialized-view") && item.logicalId === parent.id && item.namespace === operation.namespace && item.type === operation.type && item.status !== "skipped") !== void 0;
|
|
857
|
+
}
|
|
858
|
+
function emission(ok, dialect, statements, diagnostics) {
|
|
859
|
+
const frozenStatements = Object.freeze(statements.slice().sort((left, right) => left.position - right.position || left.operationId.localeCompare(right.operationId)).map((statement) => Object.freeze({
|
|
860
|
+
...statement,
|
|
861
|
+
parameters: Object.freeze([...statement.parameters])
|
|
862
|
+
})));
|
|
863
|
+
const sql = frozenStatements.map((statement) => statement.sql.trimEnd().endsWith(";") ? statement.sql.trimEnd() : `${statement.sql};`).join("\n");
|
|
864
|
+
return Object.freeze({
|
|
865
|
+
ok,
|
|
866
|
+
dialect,
|
|
867
|
+
statements: frozenStatements,
|
|
868
|
+
diagnostics: Object.freeze(sortDiagnostics(diagnostics)),
|
|
869
|
+
sql,
|
|
870
|
+
parameters: Object.freeze(frozenStatements.flatMap((statement) => statement.parameters))
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
function operationDiagnostic(operation, code, message) {
|
|
874
|
+
return {
|
|
875
|
+
code,
|
|
876
|
+
severity: "error",
|
|
877
|
+
message,
|
|
878
|
+
operationId: operation.id,
|
|
879
|
+
path: operation.path,
|
|
880
|
+
kind: operation.kind,
|
|
881
|
+
dialect: operation.dialect.name
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function mapPlanDiagnosticCode(code) {
|
|
885
|
+
if (code === "decision-required") return "decision-required";
|
|
886
|
+
if (code === "unknown") return "unknown";
|
|
887
|
+
if (code === "lossy") return "lossy";
|
|
888
|
+
if (code === "unsupported") return "unsupported";
|
|
889
|
+
if (code === "destructive") return "destructive";
|
|
890
|
+
if (code === "ambiguous") return "ambiguous";
|
|
891
|
+
if (code === "dialect-mismatch") return "dialect-mismatch";
|
|
892
|
+
if (code === "custom-sql") return "custom-sql";
|
|
893
|
+
if (code === "non-canonical") return "non-canonical";
|
|
894
|
+
return "invalid-plan";
|
|
895
|
+
}
|
|
896
|
+
function sortDiagnostics(diagnostics) {
|
|
897
|
+
return [...diagnostics].sort((left, right) => comparePath(left.path, right.path) || (left.operationId ?? "").localeCompare(right.operationId ?? "") || left.code.localeCompare(right.code) || left.message.localeCompare(right.message));
|
|
898
|
+
}
|
|
899
|
+
function comparePath(left, right) {
|
|
900
|
+
const length = Math.min(left.length, right.length);
|
|
901
|
+
for (let index = 0; index < length; index += 1) {
|
|
902
|
+
const a = String(left[index]);
|
|
903
|
+
const b = String(right[index]);
|
|
904
|
+
const compared = a.localeCompare(b);
|
|
905
|
+
if (compared !== 0) return compared;
|
|
906
|
+
}
|
|
907
|
+
return left.length - right.length;
|
|
908
|
+
}
|
|
909
|
+
function normalizeSql(sql) {
|
|
910
|
+
return sql.replace(/\r\n?/gu, "\n");
|
|
911
|
+
}
|
|
912
|
+
function stringValue(value) {
|
|
913
|
+
return typeof value === "string" ? value : void 0;
|
|
914
|
+
}
|
|
915
|
+
function numberValue(value) {
|
|
916
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
917
|
+
}
|
|
918
|
+
function recordValue(value) {
|
|
919
|
+
return isRecord(value) ? value : void 0;
|
|
920
|
+
}
|
|
921
|
+
function isRecord(value) {
|
|
922
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
923
|
+
}
|
|
924
|
+
function isPath(value) {
|
|
925
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string" || typeof item === "number");
|
|
926
|
+
}
|
|
927
|
+
function arrayOfRecords(value) {
|
|
928
|
+
return Array.isArray(value) ? value.filter(isRecord).map((item) => ({ ...item })) : [];
|
|
929
|
+
}
|
|
930
|
+
function stringArray(value) {
|
|
931
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
932
|
+
}
|
|
933
|
+
function expressionValue(value) {
|
|
934
|
+
if (!isRecord(value) || value.kind !== "expression" || typeof value.sql !== "string" || typeof value.expressionKind !== "string") return void 0;
|
|
935
|
+
return value;
|
|
936
|
+
}
|
|
937
|
+
function expressionsIn(value) {
|
|
938
|
+
const result = [];
|
|
939
|
+
const visit = (current) => {
|
|
940
|
+
const expression = expressionValue(current);
|
|
941
|
+
if (expression !== void 0) {
|
|
942
|
+
result.push(expression);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (Array.isArray(current)) {
|
|
946
|
+
for (const item of current) visit(item);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (isRecord(current)) for (const child of Object.values(current)) visit(child);
|
|
950
|
+
};
|
|
951
|
+
visit(value);
|
|
952
|
+
return result;
|
|
953
|
+
}
|
|
954
|
+
function storageDialect(value) {
|
|
955
|
+
const storage = recordValue(value);
|
|
956
|
+
return storage?.kind === "native" ? stringValue(storage.dialect) : void 0;
|
|
957
|
+
}
|
|
958
|
+
function renderValueFact(value, dialect) {
|
|
959
|
+
if (value.kind === "literal") return renderLiteral(value.value, dialect);
|
|
960
|
+
if (value.kind === "expression") return renderDefault(value, dialect);
|
|
961
|
+
throw new TypeError("Value fact must be a literal or expression");
|
|
962
|
+
}
|
|
963
|
+
function sameValue(left, right) {
|
|
964
|
+
if (Object.is(left, right)) return true;
|
|
965
|
+
if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((item, index) => sameValue(item, right[index]));
|
|
966
|
+
if (isRecord(left) && isRecord(right)) {
|
|
967
|
+
const leftKeys = Object.keys(left).sort();
|
|
968
|
+
const rightKeys = Object.keys(right).sort();
|
|
969
|
+
return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && sameValue(left[key], right[key]));
|
|
970
|
+
}
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
function sqlAction(action) {
|
|
974
|
+
return action.replaceAll("-", " ").toUpperCase();
|
|
975
|
+
}
|
|
976
|
+
function extensionName(value, fallback) {
|
|
977
|
+
return stringValue(value.extensionName) ?? fallback;
|
|
978
|
+
}
|
|
979
|
+
function commentTargetType(kind) {
|
|
980
|
+
switch (kind) {
|
|
981
|
+
case "table": return "TABLE";
|
|
982
|
+
case "view": return "VIEW";
|
|
983
|
+
case "materialized-view": return "MATERIALIZED VIEW";
|
|
984
|
+
case "sequence": return "SEQUENCE";
|
|
985
|
+
case "function":
|
|
986
|
+
case "routine": return "FUNCTION";
|
|
987
|
+
case "column": return "COLUMN";
|
|
988
|
+
case "schema":
|
|
989
|
+
case "namespace": return "SCHEMA";
|
|
990
|
+
case "index": return "INDEX";
|
|
991
|
+
default: return;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function dollarQuote(sql) {
|
|
995
|
+
for (const tag of [
|
|
996
|
+
"$$",
|
|
997
|
+
"$qubu$",
|
|
998
|
+
"$qubu_ddl$"
|
|
999
|
+
]) if (!sql.includes(tag)) return `${tag}${sql}${tag}`;
|
|
1000
|
+
return `'${sql.replaceAll("'", "''")}'`;
|
|
1001
|
+
}
|
|
1002
|
+
function parseVersion(value) {
|
|
1003
|
+
if (typeof value === "number") {
|
|
1004
|
+
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
1005
|
+
value = String(value);
|
|
1006
|
+
}
|
|
1007
|
+
const match = value.trim().match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/iu);
|
|
1008
|
+
if (match === null) return void 0;
|
|
1009
|
+
return match.slice(1).filter((item) => item !== void 0).map((item) => Number(item));
|
|
1010
|
+
}
|
|
1011
|
+
function compareVersion(left, right) {
|
|
1012
|
+
const length = Math.max(left.length, right.length);
|
|
1013
|
+
for (let index = 0; index < length; index += 1) {
|
|
1014
|
+
const comparison = (left[index] ?? 0) - (right[index] ?? 0);
|
|
1015
|
+
if (comparison !== 0) return comparison;
|
|
1016
|
+
}
|
|
1017
|
+
return 0;
|
|
1018
|
+
}
|
|
1019
|
+
//#endregion
|
|
1020
|
+
//#region src/ddl/mysql.ts
|
|
1021
|
+
/** MySQL operation support used by the strict DDL preflight. */
|
|
1022
|
+
const mysqlDdlEmitter = createDdlEmitter({
|
|
1023
|
+
dialect: "mysql",
|
|
1024
|
+
supports: /* @__PURE__ */ new Set([
|
|
1025
|
+
"table",
|
|
1026
|
+
"column",
|
|
1027
|
+
"constraint",
|
|
1028
|
+
"index",
|
|
1029
|
+
"view",
|
|
1030
|
+
"routine",
|
|
1031
|
+
"partition",
|
|
1032
|
+
"trigger",
|
|
1033
|
+
"comment",
|
|
1034
|
+
"generated-column"
|
|
1035
|
+
])
|
|
1036
|
+
});
|
|
1037
|
+
/** Emit a reviewed plan with MySQL's schema dialect. */
|
|
1038
|
+
function emitMysqlMigrationPlan(plan, options) {
|
|
1039
|
+
return mysqlDdlEmitter.emit(plan, mysqlSchemaDialect, options);
|
|
1040
|
+
}
|
|
1041
|
+
const emitMysqlDdl = emitMysqlMigrationPlan;
|
|
1042
|
+
//#endregion
|
|
1043
|
+
//#region src/ddl/postgres.ts
|
|
1044
|
+
/** PostgreSQL operation support used by the strict DDL preflight. */
|
|
1045
|
+
const postgresDdlEmitter = createDdlEmitter({
|
|
1046
|
+
dialect: "postgresql",
|
|
1047
|
+
supports: /* @__PURE__ */ new Set([
|
|
1048
|
+
"namespace",
|
|
1049
|
+
"table",
|
|
1050
|
+
"column",
|
|
1051
|
+
"constraint",
|
|
1052
|
+
"index",
|
|
1053
|
+
"view",
|
|
1054
|
+
"materialized-view",
|
|
1055
|
+
"sequence",
|
|
1056
|
+
"enum",
|
|
1057
|
+
"domain",
|
|
1058
|
+
"collation",
|
|
1059
|
+
"trigger",
|
|
1060
|
+
"routine",
|
|
1061
|
+
"partition",
|
|
1062
|
+
"policy",
|
|
1063
|
+
"extension",
|
|
1064
|
+
"comment",
|
|
1065
|
+
"ownership",
|
|
1066
|
+
"generated-column",
|
|
1067
|
+
"index-predicate",
|
|
1068
|
+
"index-include"
|
|
1069
|
+
])
|
|
1070
|
+
});
|
|
1071
|
+
/** Emit a reviewed plan with PostgreSQL's schema dialect. */
|
|
1072
|
+
function emitPostgresMigrationPlan(plan, options) {
|
|
1073
|
+
return postgresDdlEmitter.emit(plan, postgresSchemaDialect, options);
|
|
1074
|
+
}
|
|
1075
|
+
/** Alias using the full PostgreSQL spelling. */
|
|
1076
|
+
const emitPostgresqlMigrationPlan = emitPostgresMigrationPlan;
|
|
1077
|
+
const emitPostgresDdl = emitPostgresMigrationPlan;
|
|
1078
|
+
const emitPostgresqlDdl = emitPostgresMigrationPlan;
|
|
1079
|
+
//#endregion
|
|
1080
|
+
//#region src/ddl/sqlite.ts
|
|
1081
|
+
/** SQLite operation support used by the strict DDL preflight. */
|
|
1082
|
+
const sqliteDdlEmitter = createDdlEmitter({
|
|
1083
|
+
dialect: "sqlite",
|
|
1084
|
+
supports: /* @__PURE__ */ new Set([
|
|
1085
|
+
"table",
|
|
1086
|
+
"column",
|
|
1087
|
+
"constraint",
|
|
1088
|
+
"index",
|
|
1089
|
+
"view",
|
|
1090
|
+
"trigger",
|
|
1091
|
+
"generated-column",
|
|
1092
|
+
"index-predicate"
|
|
1093
|
+
])
|
|
1094
|
+
});
|
|
1095
|
+
/** Emit a reviewed plan with SQLite's schema dialect. */
|
|
1096
|
+
function emitSqliteMigrationPlan(plan, options) {
|
|
1097
|
+
return sqliteDdlEmitter.emit(plan, sqliteSchemaDialect, options);
|
|
1098
|
+
}
|
|
1099
|
+
const emitSqliteDdl = emitSqliteMigrationPlan;
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region src/ddl/index.ts
|
|
1102
|
+
/** Select a first-party emitter by the selected schema dialect name. */
|
|
1103
|
+
function ddlEmitterForDialect(dialect) {
|
|
1104
|
+
if (dialect.name === "postgresql") return postgresDdlEmitter;
|
|
1105
|
+
if (dialect.name === "sqlite") return sqliteDdlEmitter;
|
|
1106
|
+
if (dialect.name === "mysql") return mysqlDdlEmitter;
|
|
1107
|
+
throw new TypeError(`No first-party DDL emitter exists for "${dialect.name}"`);
|
|
1108
|
+
}
|
|
1109
|
+
/** Emit a plan through the matching first-party schema dialect. */
|
|
1110
|
+
function emitMigrationPlan(plan, dialect, options) {
|
|
1111
|
+
return ddlEmitterForDialect(dialect).emit(plan, dialect, options);
|
|
1112
|
+
}
|
|
1113
|
+
/** Convenient defaults for callers that already selected a plan dialect. */
|
|
1114
|
+
const ddlDialects = Object.freeze({
|
|
1115
|
+
postgresql: postgresSchemaDialect,
|
|
1116
|
+
sqlite: sqliteSchemaDialect,
|
|
1117
|
+
mysql: mysqlSchemaDialect
|
|
1118
|
+
});
|
|
1119
|
+
//#endregion
|
|
1120
|
+
export { createDdlEmitter, ddlDialects, ddlEmitterForDialect, emitMigrationPlan, emitMysqlDdl, emitMysqlMigrationPlan, emitPostgresDdl, emitPostgresMigrationPlan, emitPostgresqlDdl, emitPostgresqlMigrationPlan, emitSqliteDdl, emitSqliteMigrationPlan, mysqlDdlEmitter, postgresDdlEmitter, sqliteDdlEmitter };
|