@voltro/database 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +319 -0
- package/dist/fileBased-cc9IzIjU.js +1083 -0
- package/dist/index.d.ts +5107 -0
- package/dist/index.js +1505 -0
- package/dist/sql.d.ts +2023 -0
- package/dist/sql.js +2569 -0
- package/package.json +51 -0
package/dist/sql.js
ADDED
|
@@ -0,0 +1,2569 @@
|
|
|
1
|
+
import { L as e, R as t, V as n, a as r, c as i, et as a, i as o, n as s, ot as c, r as l, t as u } from "./fileBased-cc9IzIjU.js";
|
|
2
|
+
import { Effect as d } from "effect";
|
|
3
|
+
import { createLogger as f } from "@voltro/logger";
|
|
4
|
+
import { SqlClient as p } from "@effect/sql";
|
|
5
|
+
import { createHash as m } from "node:crypto";
|
|
6
|
+
import { promises as h } from "node:fs";
|
|
7
|
+
import { join as g } from "node:path";
|
|
8
|
+
import { pathToFileURL as ee } from "node:url";
|
|
9
|
+
//#region src/migrations/ddlError.ts
|
|
10
|
+
var _ = (e, t, n, r = 400) => {
|
|
11
|
+
let i = n, a = i.cause ?? {}, o = (e) => {
|
|
12
|
+
let t = a[e];
|
|
13
|
+
return t == null || t === "" ? void 0 : String(t);
|
|
14
|
+
}, s = [
|
|
15
|
+
"",
|
|
16
|
+
`═══ ${e} ═══`,
|
|
17
|
+
`statement: ${t.replace(/\s+/g, " ").slice(0, r)}${t.length > r ? " …" : ""}`,
|
|
18
|
+
`db.message: ${o("sqlMessage") ?? o("message") ?? i.message ?? "(none)"}`
|
|
19
|
+
];
|
|
20
|
+
for (let e of [
|
|
21
|
+
"code",
|
|
22
|
+
"errno",
|
|
23
|
+
"sqlState",
|
|
24
|
+
"number",
|
|
25
|
+
"state",
|
|
26
|
+
"class",
|
|
27
|
+
"lineNumber",
|
|
28
|
+
"procName",
|
|
29
|
+
"serverName",
|
|
30
|
+
"detail",
|
|
31
|
+
"hint",
|
|
32
|
+
"table",
|
|
33
|
+
"column",
|
|
34
|
+
"constraint",
|
|
35
|
+
"position",
|
|
36
|
+
"routine"
|
|
37
|
+
]) {
|
|
38
|
+
let t = o(e);
|
|
39
|
+
t !== void 0 && s.push(`db.${e}: ${t}`);
|
|
40
|
+
}
|
|
41
|
+
return s.push(""), s.join("\n");
|
|
42
|
+
}, v = (e, t) => `${e}::${t}`, y = (e, t) => {
|
|
43
|
+
let n = /* @__PURE__ */ new Set(), r = /* @__PURE__ */ new Set(), i = /* @__PURE__ */ new Set(), a = (e) => {
|
|
44
|
+
n.add(e);
|
|
45
|
+
let o = [...t(e)].filter((t) => t.target !== e).sort((e, t) => e.column.localeCompare(t.column));
|
|
46
|
+
for (let t of o) n.has(t.target) ? i.add(v(e, t.column)) : r.has(t.target) || a(t.target);
|
|
47
|
+
n.delete(e), r.add(e);
|
|
48
|
+
};
|
|
49
|
+
for (let t of [...e].sort()) r.has(t) || a(t);
|
|
50
|
+
return i;
|
|
51
|
+
}, b = 6322741009312437n, x = 250, S = () => {
|
|
52
|
+
let e = Number(process.env.VOLTRO_MIGRATION_LOCK_TIMEOUT_MS);
|
|
53
|
+
return Number.isFinite(e) && e > 0 ? e : 3e4;
|
|
54
|
+
}, C = (e) => d.gen(function* () {
|
|
55
|
+
let t = S(), n = Date.now();
|
|
56
|
+
for (;;) {
|
|
57
|
+
if ((yield* e`
|
|
58
|
+
SELECT pg_try_advisory_lock(${"6322741009312437"}::bigint) AS got`)[0]?.got) return;
|
|
59
|
+
Date.now() - n >= t && (yield* d.die(/* @__PURE__ */ Error(`could not acquire the postgres migration advisory lock within ${Math.round(t / 1e3)}s. Another migration is in progress, or a prior boot crashed while holding it. (A crashed process's session-level advisory lock is released once its DB connection closes; if this persists, find + terminate the holder via pg_stat_activity, or raise VOLTRO_MIGRATION_LOCK_TIMEOUT_MS.)`))), yield* d.sleep(`${x} millis`);
|
|
60
|
+
}
|
|
61
|
+
}), te = (e) => d.gen(function* () {
|
|
62
|
+
yield* e`SELECT pg_advisory_unlock(${b.toString()}::bigint)`;
|
|
63
|
+
}), ne = `voltro_migration_${b.toString(36)}`, re = 30, ie = (e) => d.gen(function* () {
|
|
64
|
+
(yield* e`
|
|
65
|
+
SELECT GET_LOCK(${ne}, ${re}) AS got`)[0]?.got !== 1 && (yield* d.die(/* @__PURE__ */ Error(`could not acquire the migration lock '${ne}' within ${re}s (another migration is in progress, or a prior one left it held).`)));
|
|
66
|
+
}), ae = (e) => d.gen(function* () {
|
|
67
|
+
yield* e`SELECT RELEASE_LOCK(${ne})`;
|
|
68
|
+
}), oe = `voltro_migration_${b.toString(36)}`, se = 3e4, ce = (e) => d.gen(function* () {
|
|
69
|
+
let t = yield* e`
|
|
70
|
+
DECLARE @res int;
|
|
71
|
+
EXEC @res = sp_getapplock @Resource = ${oe}, @LockMode = 'Exclusive',
|
|
72
|
+
@LockOwner = 'Session', @LockTimeout = ${se};
|
|
73
|
+
SELECT @res AS res`;
|
|
74
|
+
(t[0]?.res ?? -999) < 0 && (yield* d.die(/* @__PURE__ */ Error(`could not acquire the migration lock '${oe}' (sp_getapplock returned ${t[0]?.res ?? "no row"}; another migration is in progress).`)));
|
|
75
|
+
}), le = (e) => d.gen(function* () {
|
|
76
|
+
yield* e`EXEC sp_releaseapplock @Resource = ${oe}, @LockOwner = 'Session'`;
|
|
77
|
+
}), ue = d.void, w = (e) => e.onDialectOrElse({
|
|
78
|
+
mysql: () => ie(e),
|
|
79
|
+
mssql: () => ce(e),
|
|
80
|
+
sqlite: () => ue,
|
|
81
|
+
orElse: () => C(e)
|
|
82
|
+
}), T = (e) => e.onDialectOrElse({
|
|
83
|
+
mysql: () => ae(e),
|
|
84
|
+
mssql: () => le(e),
|
|
85
|
+
sqlite: () => ue,
|
|
86
|
+
orElse: () => te(e)
|
|
87
|
+
}), E = (e, t) => d.acquireUseRelease(w(e), () => t, () => T(e).pipe(d.orDie)), de = f({ scope: "voltro:migrate" }), D = (e) => {
|
|
88
|
+
de.warn(e.replace(/^\[voltro:migrate\]\s*/, ""));
|
|
89
|
+
}, O = (t, n) => {
|
|
90
|
+
let r = t.type;
|
|
91
|
+
if (t.spatial) {
|
|
92
|
+
if (n === "postgres") return `${t.spatial.kind}(${t.spatial.geomKind},${t.spatial.srid})`;
|
|
93
|
+
throw Error(`@voltro/plugin-postgis: ${t.spatial.kind}('${t.spatial.geomKind}', ${t.spatial.srid}) is postgres-only — the active dialect is '${n}'. PostGIS has no portable equivalent; use store: 'postgres' (or DB_DIALECT=postgres) for spatial columns, or model the value as plain text/json.`);
|
|
94
|
+
}
|
|
95
|
+
if (r === "raw") {
|
|
96
|
+
if (t.rawDdl === void 0) throw Error("raw() column is missing its DDL fragment — this is a framework bug.");
|
|
97
|
+
return t.rawDdl;
|
|
98
|
+
}
|
|
99
|
+
if (r === "array" && t.arrayElement) {
|
|
100
|
+
if (n === "postgres") return `${O({
|
|
101
|
+
...t,
|
|
102
|
+
type: t.arrayElement,
|
|
103
|
+
arrayElement: void 0
|
|
104
|
+
}, n)}[]`;
|
|
105
|
+
if (n === "mysql" || n === "mariadb") return "JSON";
|
|
106
|
+
if (n === "mssql") return "NVARCHAR(MAX)";
|
|
107
|
+
if (e(n)) return "TEXT";
|
|
108
|
+
}
|
|
109
|
+
if (r === "vector") {
|
|
110
|
+
let r = t.vectorDim;
|
|
111
|
+
if (r === void 0) throw Error("vector column is missing its dimension — this is a framework bug.");
|
|
112
|
+
let i = t.vectorPrecision === "half";
|
|
113
|
+
if (n === "postgres") return i ? `HALFVEC(${r})` : `VECTOR(${r})`;
|
|
114
|
+
if (n === "mysql" || n === "mariadb") return `VECTOR(${r})`;
|
|
115
|
+
if (n === "mssql") return "VARBINARY(MAX)";
|
|
116
|
+
if (e(n)) return "BLOB";
|
|
117
|
+
}
|
|
118
|
+
if (r === "interval") {
|
|
119
|
+
if (n === "postgres") return "INTERVAL";
|
|
120
|
+
if (n === "mysql" || n === "mariadb" || n === "mssql") return "BIGINT";
|
|
121
|
+
if (e(n)) return "INTEGER";
|
|
122
|
+
}
|
|
123
|
+
if (r === "decimal") {
|
|
124
|
+
let r = t.precision, i = t.scale ?? 0;
|
|
125
|
+
if (r === void 0) throw Error("decimal column is missing its precision — this is a framework bug.");
|
|
126
|
+
return e(n) ? "NUMERIC" : n === "postgres" ? `NUMERIC(${r}, ${i})` : `DECIMAL(${r}, ${i})`;
|
|
127
|
+
}
|
|
128
|
+
if (r === "bigint") return e(n) ? "INTEGER" : "BIGINT";
|
|
129
|
+
if (r === "enum") {
|
|
130
|
+
if (n === "postgres") return t.enumName ?? "TEXT";
|
|
131
|
+
if (n === "mysql" || n === "mariadb") return `ENUM(${(t.enumValues ?? []).map((e) => `'${e.replace(/'/g, "''")}'`).join(", ")})`;
|
|
132
|
+
if (n === "mssql") return "NVARCHAR(255)";
|
|
133
|
+
if (e(n)) return "TEXT";
|
|
134
|
+
}
|
|
135
|
+
if (r === "text" && t.maxLength !== void 0 && t.generatedAs === void 0) return n === "mssql" ? `NVARCHAR(${t.maxLength})` : e(n) ? "TEXT" : `VARCHAR(${t.maxLength})`;
|
|
136
|
+
if (e(n)) switch (r) {
|
|
137
|
+
case "id": return "TEXT";
|
|
138
|
+
case "text": return "TEXT";
|
|
139
|
+
case "integer": return "INTEGER";
|
|
140
|
+
case "real": return "REAL";
|
|
141
|
+
case "boolean": return "INTEGER";
|
|
142
|
+
case "timestamp": return "TEXT";
|
|
143
|
+
case "date": return "TEXT";
|
|
144
|
+
case "json": return "TEXT";
|
|
145
|
+
case "bytes": return "BLOB";
|
|
146
|
+
case "reference": return "TEXT";
|
|
147
|
+
case "vector": return "BLOB";
|
|
148
|
+
case "enum": return "TEXT";
|
|
149
|
+
case "array": return "TEXT";
|
|
150
|
+
case "interval": return "INTEGER";
|
|
151
|
+
}
|
|
152
|
+
if (n === "mysql" || n === "mariadb") switch (r) {
|
|
153
|
+
case "id": return "VARCHAR(64)";
|
|
154
|
+
case "text": return t.hasDefault && t.defaultFactory === void 0 || t.oneOf && t.oneOf.length > 0 ? "VARCHAR(255)" : t.generatedAs ? "TEXT" : "LONGTEXT";
|
|
155
|
+
case "integer": return "INT";
|
|
156
|
+
case "real": return "DOUBLE";
|
|
157
|
+
case "boolean": return "TINYINT(1)";
|
|
158
|
+
case "timestamp": return "DATETIME(6)";
|
|
159
|
+
case "date": return "DATE";
|
|
160
|
+
case "json": return "JSON";
|
|
161
|
+
case "bytes": return "LONGBLOB";
|
|
162
|
+
case "reference": return "VARCHAR(64)";
|
|
163
|
+
case "vector": return "VECTOR(0)";
|
|
164
|
+
case "enum": return "TEXT";
|
|
165
|
+
case "array": return "JSON";
|
|
166
|
+
case "interval": return "BIGINT";
|
|
167
|
+
}
|
|
168
|
+
if (n === "mssql") switch (r) {
|
|
169
|
+
case "id": return "NVARCHAR(64)";
|
|
170
|
+
case "text": return t.hasDefault && t.defaultFactory === void 0 || t.oneOf && t.oneOf.length > 0 ? "NVARCHAR(450)" : "NVARCHAR(MAX)";
|
|
171
|
+
case "integer": return "INT";
|
|
172
|
+
case "real": return "FLOAT";
|
|
173
|
+
case "boolean": return "BIT";
|
|
174
|
+
case "timestamp": return "DATETIME2(6)";
|
|
175
|
+
case "date": return "DATE";
|
|
176
|
+
case "json": return "NVARCHAR(MAX)";
|
|
177
|
+
case "bytes": return "VARBINARY(MAX)";
|
|
178
|
+
case "reference": return "NVARCHAR(64)";
|
|
179
|
+
case "vector": return "VARBINARY(MAX)";
|
|
180
|
+
case "enum": return "NVARCHAR(255)";
|
|
181
|
+
case "array": return "NVARCHAR(MAX)";
|
|
182
|
+
case "interval": return "BIGINT";
|
|
183
|
+
}
|
|
184
|
+
switch (r) {
|
|
185
|
+
case "id": return "TEXT";
|
|
186
|
+
case "text": return "TEXT";
|
|
187
|
+
case "integer": return "INTEGER";
|
|
188
|
+
case "real": return "DOUBLE PRECISION";
|
|
189
|
+
case "boolean": return "BOOLEAN";
|
|
190
|
+
case "timestamp": return "TIMESTAMPTZ";
|
|
191
|
+
case "date": return "DATE";
|
|
192
|
+
case "json": return "JSONB";
|
|
193
|
+
case "bytes": return "BYTEA";
|
|
194
|
+
case "reference": return "TEXT";
|
|
195
|
+
case "vector": return "VECTOR";
|
|
196
|
+
case "enum": return t.enumName ?? "TEXT";
|
|
197
|
+
case "array": return "TEXT[]";
|
|
198
|
+
case "interval": return "INTERVAL";
|
|
199
|
+
}
|
|
200
|
+
}, fe = (t, n) => {
|
|
201
|
+
if (!t.hasDefault || t.defaultFactory !== void 0) return null;
|
|
202
|
+
let r = t.defaultValue;
|
|
203
|
+
return r === "now" ? e(n) ? "DEFAULT CURRENT_TIMESTAMP" : n === "mssql" ? "DEFAULT SYSUTCDATETIME()" : n === "mysql" || n === "mariadb" ? "DEFAULT CURRENT_TIMESTAMP(6)" : "DEFAULT now()" : typeof r == "boolean" ? n === "postgres" ? `DEFAULT ${r}` : `DEFAULT ${+!!r}` : typeof r == "number" ? `DEFAULT ${r}` : typeof r == "string" ? `DEFAULT '${r.replace(/'/g, "''")}'` : typeof r == "object" && r && !Array.isArray(r) ? k(r, n) : null;
|
|
204
|
+
}, k = (e, t) => {
|
|
205
|
+
let n = JSON.stringify(e).replace(/'/g, "''");
|
|
206
|
+
return t === "postgres" ? `DEFAULT '${n}'::jsonb` : t === "mysql" || t === "mariadb" ? `DEFAULT (CAST('${n}' AS JSON))` : `DEFAULT '${n}'`;
|
|
207
|
+
}, A = (e, t) => {
|
|
208
|
+
switch (t) {
|
|
209
|
+
case "postgres": return `"${e}" BIGSERIAL PRIMARY KEY`;
|
|
210
|
+
case "mysql":
|
|
211
|
+
case "mariadb": return `\`${e}\` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY`;
|
|
212
|
+
case "mssql": return `[${e}] BIGINT IDENTITY(1,1) PRIMARY KEY`;
|
|
213
|
+
case "sqlite": return `"${e}" INTEGER PRIMARY KEY AUTOINCREMENT`;
|
|
214
|
+
case "turso": return `"${e}" INTEGER PRIMARY KEY`;
|
|
215
|
+
}
|
|
216
|
+
}, j = (e, t) => {
|
|
217
|
+
if (e === "restrict" && t === "mssql") return "NO ACTION";
|
|
218
|
+
switch (e) {
|
|
219
|
+
case "cascade": return "CASCADE";
|
|
220
|
+
case "restrict": return "RESTRICT";
|
|
221
|
+
case "setNull": return "SET NULL";
|
|
222
|
+
case "noAction": return "NO ACTION";
|
|
223
|
+
}
|
|
224
|
+
}, pe = (e, n, r) => r === null ? t(e, n) : `${t(r, n)}.${t(e, n)}`, me = 191, he = (e, n, r) => {
|
|
225
|
+
let i = t(e, r);
|
|
226
|
+
if (r !== "mysql" && r !== "mariadb") return i;
|
|
227
|
+
let a = n[e];
|
|
228
|
+
if (!a) return i;
|
|
229
|
+
let o = O(a, r).toUpperCase();
|
|
230
|
+
return o.endsWith("TEXT") || o.endsWith("BLOB") ? `${i}(${me})` : i;
|
|
231
|
+
}, ge = (e, t) => {
|
|
232
|
+
if (!e.generatedAs) return null;
|
|
233
|
+
let { expr: n, stored: r } = e.generatedAs;
|
|
234
|
+
if (t === "postgres") return r || D("[voltro:migrate] generated column requested as VIRTUAL but postgres only supports STORED — promoting"), `GENERATED ALWAYS AS (${n}) STORED`;
|
|
235
|
+
if (t === "mysql" || t === "mariadb") return `GENERATED ALWAYS AS (${n}) ${r ? "STORED" : "VIRTUAL"}`;
|
|
236
|
+
if (t === "mssql") return `AS (${n})${r ? " PERSISTED" : ""}`;
|
|
237
|
+
if (t === "turso") throw Error("@voltro/sql-turso: generated columns (.generatedAs(...)) are not supported — Turso's MVCC mode (journal_mode=experimental_mvcc) rejects them. Drop the generated column, or use the 'sqlite' / 'postgres' dialect for this table.");
|
|
238
|
+
return `GENERATED ALWAYS AS (${n}) ${r ? "STORED" : "VIRTUAL"}`;
|
|
239
|
+
}, _e = (n, r, i, a, o = !1, s = !1) => {
|
|
240
|
+
if (r.type === "id" && r.idScheme?.kind === "numeric") return A(n, i);
|
|
241
|
+
let c = i === "mssql" && r.type === "text" && !r.generatedAs && r.maxLength === void 0 && a?.has(n) ? "NVARCHAR(450)" : O(r, i), l = [t(n, i), c];
|
|
242
|
+
r.type === "id" && !s && l.push("PRIMARY KEY");
|
|
243
|
+
let u = ge(r, i);
|
|
244
|
+
if (u) l.push(u);
|
|
245
|
+
else {
|
|
246
|
+
r.nullable || l.push("NOT NULL"), r.unique && r.type !== "id" && l.push("UNIQUE");
|
|
247
|
+
let e = fe(r, i);
|
|
248
|
+
e && l.push(e);
|
|
249
|
+
}
|
|
250
|
+
if (r.type === "reference" && r.references && !o) {
|
|
251
|
+
let e = r.references();
|
|
252
|
+
l.push(`REFERENCES ${t(e.tableName, i)} (${t("id", i)})`);
|
|
253
|
+
let n = j(r.onDelete ?? "restrict", i);
|
|
254
|
+
l.push(`ON DELETE ${n}`);
|
|
255
|
+
let a = j(r.refOnUpdate ?? "noAction", i);
|
|
256
|
+
a !== "NO ACTION" && l.push(`ON UPDATE ${a}`);
|
|
257
|
+
}
|
|
258
|
+
if (r.oneOf && r.oneOf.length > 0) {
|
|
259
|
+
let e = r.oneOf.map((e) => `'${e.replace(/'/g, "''")}'`).join(", ");
|
|
260
|
+
l.push(`CHECK (${t(n, i)} IN (${e}))`);
|
|
261
|
+
}
|
|
262
|
+
if (r.checks && r.checks.length > 0) for (let e of r.checks) l.push(`CHECK (${e})`);
|
|
263
|
+
if (r.type === "enum" && r.enumValues && r.enumValues.length > 0 && (i === "mssql" || e(i))) {
|
|
264
|
+
let e = r.enumValues.map((e) => `'${e.replace(/'/g, "''")}'`).join(", ");
|
|
265
|
+
l.push(`CHECK (${t(n, i)} IN (${e}))`);
|
|
266
|
+
}
|
|
267
|
+
if (r.type === "json" || r.type === "array") {
|
|
268
|
+
let r = t(n, i);
|
|
269
|
+
e(i) ? l.push(`CHECK (${r} IS NULL OR json_valid(${r}))`) : i === "mssql" && l.push(`CHECK (${r} IS NULL OR ISJSON(${r}) = 1)`);
|
|
270
|
+
}
|
|
271
|
+
return l.join(" ");
|
|
272
|
+
}, ve = (e, n, r = null, i) => {
|
|
273
|
+
let a = /* @__PURE__ */ new Set();
|
|
274
|
+
for (let t of e.appliedIndexes ?? []) for (let e of t.fields) typeof e == "string" && a.add(e);
|
|
275
|
+
for (let t of e.appliedUniques ?? []) for (let e of t.fields) typeof e == "string" && a.add(e);
|
|
276
|
+
let o = e.appliedPrimaryKey ?? [], s = o.length > 0, c = Object.entries(e.fields).map(([e, t]) => ` ${_e(e, t, n, a, i?.has(e) ?? !1, s)}`).join(",\n"), l = (e.appliedUniques ?? []).map((r) => {
|
|
277
|
+
let i = r.fields.map((t) => he(t, e.fields, n)).join(", ");
|
|
278
|
+
return ` CONSTRAINT ${t(r.name, n)} UNIQUE (${i})`;
|
|
279
|
+
}), u = (e.appliedChecks ?? []).map((e) => ` CONSTRAINT ${t(e.name, n)} CHECK (${e.expr})`), d = [
|
|
280
|
+
...s ? [` PRIMARY KEY (${o.map((t) => he(t, e.fields, n)).join(", ")})`] : [],
|
|
281
|
+
...l,
|
|
282
|
+
...u
|
|
283
|
+
], f = d.length > 0 ? `${c},\n${d.join(",\n")}` : c, p = pe(e.tableName, n, r);
|
|
284
|
+
if (n === "mssql") {
|
|
285
|
+
let t = r === null ? e.tableName : `${r}.${e.tableName}`, n = `CREATE TABLE ${p} (\n${f}\n)`;
|
|
286
|
+
return `IF OBJECT_ID('${t.replace(/'/g, "''")}', 'U') IS NULL EXEC(N'${n.replace(/'/g, "''")}');`;
|
|
287
|
+
}
|
|
288
|
+
return [
|
|
289
|
+
`CREATE TABLE IF NOT EXISTS ${p} (`,
|
|
290
|
+
f,
|
|
291
|
+
");"
|
|
292
|
+
].join("\n");
|
|
293
|
+
}, ye = (e, n, r) => {
|
|
294
|
+
let i = `${e}_${n.name}_fts`, a = (e) => t(e, r), o = n.columns.map(a).join(", "), s = n.columns.map((e) => `new.${a(e)}`).join(", "), c = [];
|
|
295
|
+
return c.push(`CREATE VIRTUAL TABLE IF NOT EXISTS ${a(i)} USING fts5(${o}, row_id UNINDEXED, tokenize='porter');`), c.push(`CREATE TRIGGER IF NOT EXISTS ${a(`${i}_ai`)} AFTER INSERT ON ${a(e)} BEGIN\n INSERT INTO ${a(i)} (${o}, row_id) VALUES (${s}, new.${a("id")});\nEND;`), c.push(`CREATE TRIGGER IF NOT EXISTS ${a(`${i}_ad`)} AFTER DELETE ON ${a(e)} BEGIN\n DELETE FROM ${a(i)} WHERE row_id = old.${a("id")};\nEND;`), c.push(`CREATE TRIGGER IF NOT EXISTS ${a(`${i}_au`)} AFTER UPDATE ON ${a(e)} BEGIN\n DELETE FROM ${a(i)} WHERE row_id = old.${a("id")};\n INSERT INTO ${a(i)} (${o}, row_id) VALUES (${s}, new.${a("id")});\nEND;`), c;
|
|
296
|
+
}, be = (e, r, i = null) => {
|
|
297
|
+
let a = [], s = /* @__PURE__ */ new Set(), c = (e) => e.join("\0"), l = (e, t, i, a) => {
|
|
298
|
+
let o = {
|
|
299
|
+
using: "",
|
|
300
|
+
withSuffix: "",
|
|
301
|
+
skip: !1
|
|
302
|
+
};
|
|
303
|
+
if (i === void 0 || i === "btree") return o;
|
|
304
|
+
if (i === "gist") return r === "postgres" ? {
|
|
305
|
+
using: " USING GIST",
|
|
306
|
+
withSuffix: "",
|
|
307
|
+
skip: !1
|
|
308
|
+
} : (D(`[voltro:migrate] index '${e}' on '${t}': ${r} doesn't support GiST — falling back to btree. Spatial queries on this column will fall back to sequential scan.`), o);
|
|
309
|
+
if (i === "gin") return r === "postgres" ? {
|
|
310
|
+
using: " USING GIN",
|
|
311
|
+
withSuffix: "",
|
|
312
|
+
skip: !1
|
|
313
|
+
} : r === "mysql" || r === "mariadb" ? (D(`[voltro:migrate] gin index '${e}' on '${t}': ${r} has no GIN access method — skipping (a btree on a JSON/array column can't serve containment/membership queries). Add a generated column for the specific path you query, then index that with .expressionIndex(...).`), {
|
|
314
|
+
using: "",
|
|
315
|
+
withSuffix: "",
|
|
316
|
+
skip: !0
|
|
317
|
+
}) : (D(`[voltro:migrate] gin index '${e}' on '${t}': ${r} has no GIN access method — falling back to btree. Containment/membership queries won't use this index efficiently.`), o);
|
|
318
|
+
if (i === "brin") return r === "postgres" ? {
|
|
319
|
+
using: " USING BRIN",
|
|
320
|
+
withSuffix: "",
|
|
321
|
+
skip: !1
|
|
322
|
+
} : (D(`[voltro:migrate] brin index '${e}' on '${t}': ${r} has no BRIN access method — falling back to btree (larger on disk, but the range scan still works).`), o);
|
|
323
|
+
if (i === "hnsw") {
|
|
324
|
+
if (r === "postgres") {
|
|
325
|
+
let e = a?.hnsw?.m ?? 16, t = a?.hnsw?.efConstruction ?? 64, r = a?.hnsw?.opclass ?? n(a?.hnsw?.distance);
|
|
326
|
+
return {
|
|
327
|
+
using: " USING hnsw",
|
|
328
|
+
withSuffix: ` WITH (m = ${e}, ef_construction = ${t})`,
|
|
329
|
+
opclass: r,
|
|
330
|
+
skip: !1
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return D(`[voltro:migrate] hnsw index '${e}' on '${t}': ${r} has no pgvector / HNSW access method — skipping. Nearest-neighbour queries on this column fall back to a sequential scan.`), {
|
|
334
|
+
using: "",
|
|
335
|
+
withSuffix: "",
|
|
336
|
+
skip: !0
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return o;
|
|
340
|
+
}, u = (t, n) => {
|
|
341
|
+
let i = t.map((t) => {
|
|
342
|
+
if (typeof t == "string") return he(t, e.fields, r);
|
|
343
|
+
if ("jsonPath" in t) {
|
|
344
|
+
let { column: e, path: n, numeric: i } = t.jsonPath;
|
|
345
|
+
return `(${o(e, n, r, { numeric: i })})`;
|
|
346
|
+
}
|
|
347
|
+
return `(${t.expr})`;
|
|
348
|
+
});
|
|
349
|
+
return n !== void 0 && i.length === 1 ? `${i[0]} ${n}` : i.join(", ");
|
|
350
|
+
}, d = (e, n, a, o, s, c = "") => {
|
|
351
|
+
let d = l(e, n, o, s);
|
|
352
|
+
if (d.skip) return "";
|
|
353
|
+
let f = u(a, d.opclass), p = `${d.withSuffix}${c}`, m = pe(n, r, i);
|
|
354
|
+
return r === "mssql" ? `IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = '${e}') CREATE INDEX ${t(e, r)} ON ${m}${d.using} (${f})${p};` : r === "mysql" ? `CREATE INDEX ${t(e, r)} ON ${m}${d.using} (${f})${p};` : `CREATE INDEX IF NOT EXISTS ${t(e, r)} ON ${m}${d.using} (${f})${p};`;
|
|
355
|
+
};
|
|
356
|
+
for (let n of e.appliedFullText ?? []) if (r === "postgres") {
|
|
357
|
+
let i = n.config ?? "english", o = `${n.name}_tsv`, s = n.columns.map((e) => {
|
|
358
|
+
let a = n.weights?.[e] ?? "A";
|
|
359
|
+
return `setweight(to_tsvector('${i}', coalesce(${t(e, r)}, '')), '${a}')`;
|
|
360
|
+
}).join(" || ");
|
|
361
|
+
a.push(`ALTER TABLE ${t(e.tableName, r)} ADD COLUMN IF NOT EXISTS ${t(o, r)} tsvector GENERATED ALWAYS AS (${s}) STORED;`), a.push(`CREATE INDEX IF NOT EXISTS ${t(n.name, r)} ON ${t(e.tableName, r)} USING GIN (${t(o, r)});`);
|
|
362
|
+
} else if (r === "mysql" || r === "mariadb") {
|
|
363
|
+
let i = n.columns.map((e) => t(e, r)).join(", ");
|
|
364
|
+
a.push(`CREATE FULLTEXT INDEX ${t(n.name, r)} ON ${t(e.tableName, r)} (${i});`);
|
|
365
|
+
} else if (r === "turso") throw Error(`@voltro/sql-turso: full-text index '${n.name}' on '${e.tableName}' is not supported — Turso's MVCC mode has no FTS5 virtual tables. Drop .fullTextIndex(), or use the 'sqlite' / 'postgres' dialect for this table.`);
|
|
366
|
+
else r === "sqlite" ? a.push(...ye(e.tableName, n, r)) : D(`[voltro:migrate] full-text index '${n.name}' on '${e.tableName}': ${r} uses a ranked LIKE search at query time (no native FTS catalog required).`);
|
|
367
|
+
for (let t of e.appliedIndexes) {
|
|
368
|
+
if ((r === "mariadb" || r === "mssql") && t.fields.some((e) => typeof e != "string" && "jsonPath" in e)) {
|
|
369
|
+
D(`[voltro:migrate] json-path index '${t.name}' on '${e.tableName}': ${r} cannot index a JSON expression directly — skipping. For an indexed JSON path on ${r}, add a generated column for the path (text().generatedAs(...) / .stored()) and .index([...]) it, then filter on that column instead of jsonField(...).`);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
let n = "";
|
|
373
|
+
t.where !== void 0 && t.where.length > 0 && (r === "mysql" || r === "mariadb" ? D(`[voltro:migrate] partial index '${t.name}' on '${e.tableName}': ${r} does not support WHERE clauses on indexes — emitting a full index`) : n = ` WHERE ${t.where}`);
|
|
374
|
+
let i = d(t.name, e.tableName, t.fields, t.kind, t.kindOptions, n);
|
|
375
|
+
i !== "" && a.push(i), t.fields.every((e) => typeof e == "string") && s.add(c(t.fields));
|
|
376
|
+
}
|
|
377
|
+
for (let [t, n] of Object.entries(e.fields)) {
|
|
378
|
+
if (n.type !== "reference" || n.fkAutoIndex === !1 || s.has(c([t]))) continue;
|
|
379
|
+
let r = `${e.tableName}_${t}_idx`;
|
|
380
|
+
a.push(d(r, e.tableName, [t])), s.add(c([t]));
|
|
381
|
+
}
|
|
382
|
+
return a.join("\n");
|
|
383
|
+
}, xe = "CREATE OR REPLACE FUNCTION framework_notify_change() RETURNS trigger AS $$\nDECLARE\n payload TEXT;\nBEGIN\n payload := json_build_object(\n 'table', TG_TABLE_NAME,\n -- The schema the write landed in. Under namespace isolation this is\n -- the tenant schema (tenant_<id>); the CDC consumer uses it so a\n -- write in tenant A namespace does not spuriously wake tenant B\n -- subscriptions on the same-named table. public/absent = shared.\n 'schema', TG_TABLE_SCHEMA,\n 'op', lower(TG_OP),\n 'old', CASE WHEN TG_OP = 'INSERT' THEN NULL ELSE row_to_json(OLD) END,\n 'new', CASE WHEN TG_OP = 'DELETE' THEN NULL ELSE row_to_json(NEW) END\n )::text;\n -- pg_notify hard-caps the payload at 8000 bytes; a wide reactive row\n -- (big text / json column) would otherwise abort the write itself.\n -- Fall back to a body-less notice — the CDC consumer treats absent\n -- old/new as \"all columns changed\" and re-queries, so correctness is\n -- preserved; only the delta-diff fast path is skipped for that write.\n IF octet_length(payload) >= 7999 THEN\n payload := json_build_object(\n 'table', TG_TABLE_NAME,\n 'schema', TG_TABLE_SCHEMA,\n 'op', lower(TG_OP),\n 'old', NULL,\n 'new', NULL,\n 'oversized', true\n )::text;\n END IF;\n PERFORM pg_notify('framework_changes', payload);\n RETURN COALESCE(NEW, OLD);\nEND;\n$$ LANGUAGE plpgsql;", Se = (e) => `
|
|
384
|
+
ALTER TABLE "${e.tableName}" REPLICA IDENTITY FULL;
|
|
385
|
+
DROP TRIGGER IF EXISTS framework_changes_${e.tableName} ON "${e.tableName}";
|
|
386
|
+
CREATE TRIGGER framework_changes_${e.tableName}
|
|
387
|
+
AFTER INSERT OR UPDATE OR DELETE ON "${e.tableName}"
|
|
388
|
+
FOR EACH ROW EXECUTE FUNCTION framework_notify_change();
|
|
389
|
+
`.trim(), Ce = (e) => e.some((e) => Object.values(e.fields).some((e) => e.type === "vector")), we = (e) => e.some((e) => Object.values(e.fields).some((e) => e.spatial !== void 0)), Te = (e) => {
|
|
390
|
+
let t = new Map(e.map((e) => [e.tableName, e])), n = y(e.map((e) => e.tableName), (e) => {
|
|
391
|
+
let n = t.get(e);
|
|
392
|
+
return n ? Object.entries(n.fields).flatMap(([e, n]) => {
|
|
393
|
+
if (n.type !== "reference" || !n.references) return [];
|
|
394
|
+
let r = n.references().tableName;
|
|
395
|
+
return t.has(r) ? [{
|
|
396
|
+
column: e,
|
|
397
|
+
target: r
|
|
398
|
+
}] : [];
|
|
399
|
+
}) : [];
|
|
400
|
+
}), r = [];
|
|
401
|
+
for (let t of e) for (let [e, i] of Object.entries(t.fields)) i.type !== "reference" || !i.references || n.has(v(t.tableName, e)) && r.push({
|
|
402
|
+
table: t.tableName,
|
|
403
|
+
column: e,
|
|
404
|
+
targetTable: i.references().tableName,
|
|
405
|
+
onDelete: i.onDelete ?? "restrict",
|
|
406
|
+
onUpdate: i.refOnUpdate ?? "noAction"
|
|
407
|
+
});
|
|
408
|
+
let i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Set(), o = [], s = (e) => {
|
|
409
|
+
if (!(i.has(e.tableName) || a.has(e.tableName))) {
|
|
410
|
+
a.add(e.tableName);
|
|
411
|
+
for (let [r, i] of Object.entries(e.fields)) {
|
|
412
|
+
if (i.type !== "reference" || !i.references || n.has(v(e.tableName, r))) continue;
|
|
413
|
+
let a = i.references();
|
|
414
|
+
if (a.tableName === e.tableName) continue;
|
|
415
|
+
let o = t.get(a.tableName);
|
|
416
|
+
o && s(o);
|
|
417
|
+
}
|
|
418
|
+
a.delete(e.tableName), i.add(e.tableName), o.push(e);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
for (let t of e) s(t);
|
|
422
|
+
return {
|
|
423
|
+
sorted: o,
|
|
424
|
+
cyclic: r
|
|
425
|
+
};
|
|
426
|
+
}, Ee = (e, n, r = null) => {
|
|
427
|
+
let i = (e) => r ? `${t(r, n)}.${t(e, n)}` : t(e, n), a = `${e.table}_${e.column}_fkey`, o = j(e.onDelete, n), s = j(e.onUpdate, n), c = ` ON DELETE ${o}${s === "NO ACTION" ? "" : ` ON UPDATE ${s}`}`, l = (r) => `ADD CONSTRAINT ${t(a, n)} FOREIGN KEY ${r ? "IF NOT EXISTS " : ""}(${t(e.column, n)}) REFERENCES ${i(e.targetTable)} (${t("id", n)})${c}`;
|
|
428
|
+
switch (n) {
|
|
429
|
+
case "postgres": return [
|
|
430
|
+
"DO $$ BEGIN",
|
|
431
|
+
" IF NOT EXISTS (SELECT 1 FROM pg_constraint",
|
|
432
|
+
` WHERE conname = '${a}' AND conrelid = '${r ? `${r}.` : ""}${e.table}'::regclass) THEN`,
|
|
433
|
+
` ALTER TABLE ${i(e.table)} ${l(!1)};`,
|
|
434
|
+
" END IF;",
|
|
435
|
+
"END $$;"
|
|
436
|
+
].join("\n");
|
|
437
|
+
case "mssql": return `IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = '${a}') ALTER TABLE ${i(e.table)} ${l(!1)};`;
|
|
438
|
+
case "mariadb": return `ALTER TABLE ${i(e.table)} ${l(!0)};`;
|
|
439
|
+
default: return `ALTER TABLE ${i(e.table)} ${l(!1)};`;
|
|
440
|
+
}
|
|
441
|
+
}, De = (t, n) => {
|
|
442
|
+
let r = /* @__PURE__ */ new Map();
|
|
443
|
+
if (e(n)) return r;
|
|
444
|
+
for (let e of t) {
|
|
445
|
+
let t = r.get(e.table) ?? /* @__PURE__ */ new Set();
|
|
446
|
+
t.add(e.column), r.set(e.table, t);
|
|
447
|
+
}
|
|
448
|
+
return r;
|
|
449
|
+
}, Oe = (e, t) => {
|
|
450
|
+
if (t !== "postgres") return "";
|
|
451
|
+
let n = /* @__PURE__ */ new Map();
|
|
452
|
+
for (let t of e) for (let e of Object.values(t.fields)) {
|
|
453
|
+
if (e.type !== "enum" || !e.enumName || !e.enumValues) continue;
|
|
454
|
+
let t = n.get(e.enumName);
|
|
455
|
+
if (t && t.length !== e.enumValues.length) throw Error(`dbEnum '${e.enumName}' declared with conflicting value sets across tables. Define it once and re-use the handle.`);
|
|
456
|
+
n.set(e.enumName, e.enumValues);
|
|
457
|
+
}
|
|
458
|
+
if (n.size === 0) return "";
|
|
459
|
+
let r = [];
|
|
460
|
+
for (let [e, t] of n) {
|
|
461
|
+
let n = t.map((e) => `'${e.replace(/'/g, "''")}'`).join(", ");
|
|
462
|
+
r.push(`DO $$ BEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = '${e.replace(/'/g, "''")}') THEN\n CREATE TYPE "${e}" AS ENUM (${n});\n END IF;\nEND $$;`);
|
|
463
|
+
}
|
|
464
|
+
return r.join("\n\n");
|
|
465
|
+
}, ke = (e, t) => {
|
|
466
|
+
if (t !== "postgres") return "";
|
|
467
|
+
let n = /* @__PURE__ */ new Set();
|
|
468
|
+
for (let t of e) for (let e of Object.values(t.fields)) e.type === "enum" && e.enumName && n.add(e.enumName);
|
|
469
|
+
let r = [];
|
|
470
|
+
for (let e of n) for (let { from: t, to: n } of c(e)) {
|
|
471
|
+
let i = e.replace(/'/g, "''"), a = t.replace(/'/g, "''"), o = n.replace(/'/g, "''");
|
|
472
|
+
r.push(`DO \$\$
|
|
473
|
+
DECLARE
|
|
474
|
+
type_oid oid;
|
|
475
|
+
BEGIN
|
|
476
|
+
SELECT oid INTO type_oid FROM pg_type WHERE typname = '${i}';\n IF type_oid IS NOT NULL\n AND EXISTS (SELECT 1 FROM pg_enum WHERE enumtypid = type_oid AND enumlabel = '${a}')\n AND NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumtypid = type_oid AND enumlabel = '${o}') THEN\n IF current_setting('server_version_num')::int >= 140000 THEN\n EXECUTE format('ALTER TYPE %I RENAME VALUE %L TO %L', '${i}', '${a}', '${o}');\n ELSE\n UPDATE pg_enum SET enumlabel = '${o}' WHERE enumtypid = type_oid AND enumlabel = '${a}';\n END IF;\n END IF;\nEND $$;`);
|
|
477
|
+
}
|
|
478
|
+
return r.join("\n\n");
|
|
479
|
+
}, Ae = (e) => {
|
|
480
|
+
let t = [], n = [];
|
|
481
|
+
for (let i of e) r(i) ? n.push(i) : t.push(i);
|
|
482
|
+
return {
|
|
483
|
+
tables: t,
|
|
484
|
+
views: n
|
|
485
|
+
};
|
|
486
|
+
}, M = (t, n) => {
|
|
487
|
+
let { tables: r, views: a } = Ae(t), { sorted: o, cyclic: s } = Te(r), c = De(s, n), l = [];
|
|
488
|
+
n === "postgres" && Ce(o) && l.push("CREATE EXTENSION IF NOT EXISTS vector;"), n === "postgres" && we(o) && l.push("CREATE EXTENSION IF NOT EXISTS postgis;");
|
|
489
|
+
let u = ke(o, n);
|
|
490
|
+
u && l.push(u);
|
|
491
|
+
let d = Oe(o, n);
|
|
492
|
+
d && l.push(d);
|
|
493
|
+
for (let e of o) l.push(ve(e, n, null, c.get(e.tableName)));
|
|
494
|
+
if (!e(n)) for (let e of s) l.push(Ee(e, n));
|
|
495
|
+
for (let e of o) {
|
|
496
|
+
let t = be(e, n);
|
|
497
|
+
t && l.push(t);
|
|
498
|
+
}
|
|
499
|
+
let f = o.filter((e) => e.isReactive);
|
|
500
|
+
if (f.length > 0 && n === "postgres") {
|
|
501
|
+
l.push(xe);
|
|
502
|
+
for (let e of f) l.push(Se(e));
|
|
503
|
+
}
|
|
504
|
+
for (let e of a) l.push(i(e, n));
|
|
505
|
+
return l.join("\n\n") + "\n";
|
|
506
|
+
}, je = (e, n, r) => {
|
|
507
|
+
let i = t(e, n);
|
|
508
|
+
switch (n) {
|
|
509
|
+
case "postgres": return `CREATE SCHEMA IF NOT EXISTS ${i};`;
|
|
510
|
+
case "mysql":
|
|
511
|
+
case "mariadb": return `CREATE DATABASE IF NOT EXISTS ${i};`;
|
|
512
|
+
case "mssql": return `IF SCHEMA_ID('${e.replace(/'/g, "''")}') IS NULL EXEC(N'CREATE SCHEMA ${i.replace(/'/g, "''")}');`;
|
|
513
|
+
case "turso":
|
|
514
|
+
case "sqlite": return `ATTACH DATABASE '${(r ?? `${e}.db`).replace(/'/g, "''")}' AS ${i};`;
|
|
515
|
+
}
|
|
516
|
+
}, Me = (n, r, i, a) => {
|
|
517
|
+
let o = je(i, r, a), s = t(i, r);
|
|
518
|
+
if (r === "postgres") return `${o}\n\nSET search_path TO ${s};\n\n${M(n, r)}`;
|
|
519
|
+
if (r === "mysql" || r === "mariadb") return `${o}\n\nUSE ${s};\n\n${M(n, r)}`;
|
|
520
|
+
let { sorted: c, cyclic: l } = Te(n), u = De(l, r), d = [o];
|
|
521
|
+
for (let e of c) d.push(ve(e, r, i, u.get(e.tableName)));
|
|
522
|
+
if (!e(r)) for (let e of l) d.push(Ee(e, r, i));
|
|
523
|
+
for (let e of c) {
|
|
524
|
+
let t = be(e, r, i);
|
|
525
|
+
t && d.push(t);
|
|
526
|
+
}
|
|
527
|
+
return d.join("\n\n") + "\n";
|
|
528
|
+
}, Ne = (e, n) => {
|
|
529
|
+
if (n !== "postgres") return M(e, n);
|
|
530
|
+
let { sorted: r, cyclic: i } = Te(e), a = De(i, n), o = [], s = ke(r, n);
|
|
531
|
+
s && o.push(s);
|
|
532
|
+
let c = Oe(r, n);
|
|
533
|
+
c && o.push(c);
|
|
534
|
+
for (let e of r) o.push(ve(e, n, null, a.get(e.tableName)));
|
|
535
|
+
for (let e of i) o.push(Ee(e, n));
|
|
536
|
+
for (let e of r) {
|
|
537
|
+
let r = Object.entries(e.fields);
|
|
538
|
+
if (r.length === 0) continue;
|
|
539
|
+
let i = r.map(([r, i]) => {
|
|
540
|
+
let o = _e(r, i, n, void 0, a.get(e.tableName)?.has(r) ?? !1);
|
|
541
|
+
return `ALTER TABLE ${t(e.tableName, n)} ADD COLUMN IF NOT EXISTS ${o};`;
|
|
542
|
+
});
|
|
543
|
+
o.push(i.join("\n"));
|
|
544
|
+
}
|
|
545
|
+
for (let e of r) {
|
|
546
|
+
let t = be(e, n);
|
|
547
|
+
t && o.push(t);
|
|
548
|
+
}
|
|
549
|
+
let l = r.filter((e) => e.isReactive);
|
|
550
|
+
if (l.length > 0) {
|
|
551
|
+
o.push(xe);
|
|
552
|
+
for (let e of l) o.push(Se(e));
|
|
553
|
+
}
|
|
554
|
+
return o.join("\n\n") + "\n";
|
|
555
|
+
}, Pe = (e, t = "postgres") => d.gen(function* () {
|
|
556
|
+
let n = yield* p.SqlClient;
|
|
557
|
+
yield* E(n, Le(n, M(e, t), t)).pipe(d.orDie);
|
|
558
|
+
}), Fe = (e, t) => {
|
|
559
|
+
let n = t.cause ?? {};
|
|
560
|
+
return (n.errno === 1061 || n.code === "ER_DUP_KEYNAME") && /^\s*CREATE\s+(UNIQUE\s+|FULLTEXT\s+)?INDEX\b/i.test(e);
|
|
561
|
+
}, Ie = (e) => d.gen(function* () {
|
|
562
|
+
let t = yield* p.SqlClient;
|
|
563
|
+
for (let n of Re(e)) process.env.VOLTRO_MIGRATE_DEBUG === "1" && de.debug(`EXEC: ${n.replace(/\s+/g, " ").slice(0, 180)}`), yield* t.unsafe(n).pipe(d.catchIf((e) => Fe(n, e), () => d.void), d.tapError((e) => d.sync(() => {
|
|
564
|
+
de.error(_("migrate: statement failed", n, e, 200));
|
|
565
|
+
})), d.orDieWith((e) => Error(_("migrate: statement failed", n, e, 400).trim())));
|
|
566
|
+
}), Le = (e, t, n) => (n === "turso" ? Ie(t) : e.withTransaction(Ie(t))).pipe(d.orDie), Re = (e) => {
|
|
567
|
+
let t = [], n = "", r = !1;
|
|
568
|
+
for (let i = 0; i < e.length; i++) {
|
|
569
|
+
if (e.slice(i, i + 2) === "$$") {
|
|
570
|
+
r = !r, n += "$$", i += 1;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
let a = e[i];
|
|
574
|
+
if (a === ";" && !r) {
|
|
575
|
+
let e = n.trim();
|
|
576
|
+
e.length > 0 && t.push(e), n = "";
|
|
577
|
+
} else n += a;
|
|
578
|
+
}
|
|
579
|
+
let i = n.trim();
|
|
580
|
+
return i.length > 0 && t.push(i), t;
|
|
581
|
+
}, ze = async (e, t, n = "postgres") => {
|
|
582
|
+
await d.runPromise(Pe(e, n).pipe(d.provide(t)));
|
|
583
|
+
}, Be = (e, t, n = "postgres") => d.gen(function* () {
|
|
584
|
+
let r = Me(e, n, t), i = yield* p.SqlClient;
|
|
585
|
+
yield* E(i, Le(i, r, n)).pipe(d.orDie);
|
|
586
|
+
}), Ve = async (e, t, n, r = "postgres") => {
|
|
587
|
+
await d.runPromise(Be(e, t, r).pipe(d.provide(n)));
|
|
588
|
+
}, He = async (e, t, n = "postgres") => {
|
|
589
|
+
await d.runPromise(d.gen(function* () {
|
|
590
|
+
let t = Ne(e, n), r = yield* p.SqlClient;
|
|
591
|
+
yield* E(r, Le(r, t, n)).pipe(d.orDie);
|
|
592
|
+
}).pipe(d.provide(t)));
|
|
593
|
+
}, Ue = (e) => {
|
|
594
|
+
if (e == null) return e;
|
|
595
|
+
if (Array.isArray(e)) return e.map(Ue);
|
|
596
|
+
if (typeof e != "object") return e;
|
|
597
|
+
let t = {};
|
|
598
|
+
for (let n of Object.keys(e).sort()) {
|
|
599
|
+
let r = e[n];
|
|
600
|
+
r !== void 0 && (t[n] = Ue(r));
|
|
601
|
+
}
|
|
602
|
+
return t;
|
|
603
|
+
}, We = (e) => {
|
|
604
|
+
if (!e.idScheme) return e;
|
|
605
|
+
let { idScheme: t, ...n } = e;
|
|
606
|
+
return n;
|
|
607
|
+
}, Ge = (e) => [...e].map(We).sort((e, t) => e.name.localeCompare(t.name)), Ke = (e) => [...e].sort((e, t) => e.name.localeCompare(t.name)), qe = (e) => [...e].map((e) => ({
|
|
608
|
+
name: e.name,
|
|
609
|
+
columns: Ge(e.columns),
|
|
610
|
+
indexes: Ke(e.indexes)
|
|
611
|
+
})).sort((e, t) => e.name.localeCompare(t.name)), N = (e) => {
|
|
612
|
+
let t = { tables: qe(e.tables) }, n = JSON.stringify(Ue(t));
|
|
613
|
+
return m("sha256").update(n).digest("hex");
|
|
614
|
+
}, P = (e) => e.slice(0, 16), Je = (e, t) => {
|
|
615
|
+
let n = t.references ? {
|
|
616
|
+
table: t.references().tableName,
|
|
617
|
+
column: "id",
|
|
618
|
+
...t.onDelete ? { onDelete: t.onDelete } : {},
|
|
619
|
+
...t.refOnUpdate ? { onUpdate: t.refOnUpdate } : {},
|
|
620
|
+
...t.orphanPolicy ? { orphanPolicy: t.orphanPolicy } : {}
|
|
621
|
+
} : void 0;
|
|
622
|
+
return {
|
|
623
|
+
name: e,
|
|
624
|
+
type: t.type,
|
|
625
|
+
nullable: t.generatedAs ? !0 : t.nullable,
|
|
626
|
+
unique: t.unique,
|
|
627
|
+
hasDefault: t.hasDefault,
|
|
628
|
+
...t.hasDefault && t.defaultValue !== void 0 ? { defaultValue: t.defaultValue } : {},
|
|
629
|
+
...n ? { references: n } : {},
|
|
630
|
+
...t.oneOf && t.oneOf.length > 0 ? { oneOf: t.oneOf } : {},
|
|
631
|
+
...t.idScheme ? { idScheme: { kind: t.idScheme.kind } } : {},
|
|
632
|
+
...t.rawDdl === void 0 ? {} : { rawDdl: t.rawDdl },
|
|
633
|
+
...t.precision === void 0 ? {} : { precision: t.precision },
|
|
634
|
+
...t.scale === void 0 ? {} : { scale: t.scale },
|
|
635
|
+
...t.generatedAs ? { generatedAs: {
|
|
636
|
+
expr: t.generatedAs.expr,
|
|
637
|
+
stored: t.generatedAs.stored
|
|
638
|
+
} } : {},
|
|
639
|
+
...t.narrowedFrom ? { narrowedFrom: {
|
|
640
|
+
from: t.narrowedFrom.from,
|
|
641
|
+
...t.narrowedFrom.using === void 0 ? {} : { using: t.narrowedFrom.using }
|
|
642
|
+
} } : {},
|
|
643
|
+
...t.sensitive ? { sensitive: t.sensitive } : t.encrypted && !t.safe ? { sensitive: { class: "secret" } } : {},
|
|
644
|
+
...t.safe ? { safe: !0 } : {}
|
|
645
|
+
};
|
|
646
|
+
}, Ye = (e) => {
|
|
647
|
+
let t = [], n = /* @__PURE__ */ new Set(), r = (e) => {
|
|
648
|
+
n.has(e.name) || (n.add(e.name), t.push(e));
|
|
649
|
+
}, i = e.appliedPrimaryKey ?? [];
|
|
650
|
+
i.length > 0 ? r({
|
|
651
|
+
name: `${e.tableName}_pkey`,
|
|
652
|
+
table: e.tableName,
|
|
653
|
+
columns: [...i],
|
|
654
|
+
unique: !0
|
|
655
|
+
}) : e.fields.id && r({
|
|
656
|
+
name: `${e.tableName}_pkey`,
|
|
657
|
+
table: e.tableName,
|
|
658
|
+
columns: ["id"],
|
|
659
|
+
unique: !0
|
|
660
|
+
});
|
|
661
|
+
let o = e.appliedIndexes ?? [];
|
|
662
|
+
for (let t of o) {
|
|
663
|
+
let n = t.fields.map((e) => typeof e == "string" ? e : "jsonPath" in e ? `(json:${e.jsonPath.column}->${e.jsonPath.path.join(".")}${e.jsonPath.numeric ? "::num" : ""})` : `(${e.expr})`), i = t.fields.some((e) => typeof e != "string");
|
|
664
|
+
r({
|
|
665
|
+
name: t.name,
|
|
666
|
+
table: e.tableName,
|
|
667
|
+
columns: n,
|
|
668
|
+
unique: !1,
|
|
669
|
+
...i ? { expression: !0 } : {}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
let s = e.appliedUniques ?? [];
|
|
673
|
+
for (let t of s) t.fields.length < 2 || r({
|
|
674
|
+
name: t.name,
|
|
675
|
+
table: e.tableName,
|
|
676
|
+
columns: t.fields,
|
|
677
|
+
unique: !0
|
|
678
|
+
});
|
|
679
|
+
let c = /* @__PURE__ */ new Set();
|
|
680
|
+
for (let e of o) e.fields.every((e) => typeof e == "string") && c.add(e.fields.join("\0"));
|
|
681
|
+
for (let [t, n] of Object.entries(e.fields)) {
|
|
682
|
+
let i = n;
|
|
683
|
+
if (i.dropped || !i.references || i.fkAutoIndex === !1 || c.has(t)) continue;
|
|
684
|
+
let o = `${e.tableName}_${t}_idx`;
|
|
685
|
+
a(e.tableName, o, !0, [t]), r({
|
|
686
|
+
name: o,
|
|
687
|
+
table: e.tableName,
|
|
688
|
+
columns: [t],
|
|
689
|
+
unique: !1
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
return t;
|
|
693
|
+
}, Xe = (e) => {
|
|
694
|
+
let t = new Set((e.appliedUniques ?? []).filter((e) => e.fields.length === 1).map((e) => e.fields[0])), n = [];
|
|
695
|
+
for (let [r, i] of Object.entries(e.fields)) {
|
|
696
|
+
if (i.dropped) continue;
|
|
697
|
+
let e = Je(r, i);
|
|
698
|
+
n.push(t.has(r) ? {
|
|
699
|
+
...e,
|
|
700
|
+
unique: !0
|
|
701
|
+
} : e);
|
|
702
|
+
}
|
|
703
|
+
let r = e.appliedPrimaryKey ?? [];
|
|
704
|
+
return {
|
|
705
|
+
name: e.tableName,
|
|
706
|
+
columns: n,
|
|
707
|
+
indexes: Ye(e),
|
|
708
|
+
...r.length > 0 ? { primaryKey: [...r] } : {}
|
|
709
|
+
};
|
|
710
|
+
}, Ze = (e) => {
|
|
711
|
+
let t = /* @__PURE__ */ new Map();
|
|
712
|
+
for (let n of e) for (let e of n.indexes) {
|
|
713
|
+
let r = t.get(e.name);
|
|
714
|
+
if (r !== void 0 && r !== n.name) throw Error(`duplicate index name '${e.name}' across tables '${r}' and '${n.name}'. Index names must be unique per SCHEMA, not just per table — the DB creates only one, so the other never applies and 'db plan' re-emits it forever. Give each an explicit, table-prefixed name (e.g. '${n.name}_${e.name}').`);
|
|
715
|
+
t.set(e.name, n.name);
|
|
716
|
+
}
|
|
717
|
+
}, F = (e) => {
|
|
718
|
+
let t = e.map(Xe);
|
|
719
|
+
return Ze(t), { tables: t };
|
|
720
|
+
}, Qe = /* @__PURE__ */ new Set(["voltro_isr_cache"]), $e = (e) => Qe.has(e) || e.startsWith("_voltro_") || e.startsWith("_cloud_") || e.startsWith("cluster_"), et = {
|
|
721
|
+
postgres: !0,
|
|
722
|
+
mysql: !0,
|
|
723
|
+
mariadb: !0,
|
|
724
|
+
mssql: !0,
|
|
725
|
+
sqlite: !0
|
|
726
|
+
}, tt = {
|
|
727
|
+
postgres: !0,
|
|
728
|
+
mysql: !1,
|
|
729
|
+
mariadb: !1,
|
|
730
|
+
mssql: !0,
|
|
731
|
+
sqlite: !0
|
|
732
|
+
}, nt = (e) => {
|
|
733
|
+
switch (e) {
|
|
734
|
+
case "add-column":
|
|
735
|
+
case "drop-column":
|
|
736
|
+
case "rename-column":
|
|
737
|
+
case "alter-column-nullability":
|
|
738
|
+
case "alter-column-type":
|
|
739
|
+
case "add-foreign-key":
|
|
740
|
+
case "add-unique":
|
|
741
|
+
case "add-unique-composite":
|
|
742
|
+
case "add-check": return tt;
|
|
743
|
+
default: return et;
|
|
744
|
+
}
|
|
745
|
+
}, rt = (e, t) => {
|
|
746
|
+
let n = new Map(t.tables.map((e) => [e.name, e]));
|
|
747
|
+
return new Map(e.map((e) => [e.tableName, {
|
|
748
|
+
table: e,
|
|
749
|
+
snapshot: n.get(e.tableName),
|
|
750
|
+
columnDefs: e.fields
|
|
751
|
+
}]));
|
|
752
|
+
}, it = (e) => (t, n) => t.name === e ? -1 : n.name === e ? 1 : t.name.localeCompare(n.name), I = (e) => {
|
|
753
|
+
if (typeof e != "object" || !e) return JSON.stringify(e);
|
|
754
|
+
if (Array.isArray(e)) return `[${e.map(I).join(",")}]`;
|
|
755
|
+
let t = e;
|
|
756
|
+
return `{${Object.keys(t).sort().map((e) => `${JSON.stringify(e)}:${I(t[e])}`).join(",")}}`;
|
|
757
|
+
}, at = (e) => {
|
|
758
|
+
if (!e.hasDefault || e.defaultValue === void 0 || e.defaultValue === null) return null;
|
|
759
|
+
let t = e.defaultValue;
|
|
760
|
+
if (typeof t == "boolean") return t ? "__BOOL_TRUE__" : "__BOOL_FALSE__";
|
|
761
|
+
if (typeof t == "number") return String(t);
|
|
762
|
+
if (typeof t != "string") return I(t);
|
|
763
|
+
let n = t.trim(), r = n.toLowerCase().replace(/\s+/g, "");
|
|
764
|
+
if (r === "now" || r === "now()" || /^current_timestamp(\(\d*\))?$/.test(r) || r === "sysutcdatetime()" || r === "sysdatetime()" || r === "getutcdate()" || r === "getdate()") return "__NOW__";
|
|
765
|
+
let i = n.replace(/::[a-zA-Z0-9_ "]+$/, "").trim(), a = /^'(.*)'$/.test(i) ? i.slice(1, -1).replace(/''/g, "'") : i, o = /^cast\(\s*(?:_\w+\s*)?\\?'(.*?)\\?'\s+as\s+json\s*\)$/i.exec(a);
|
|
766
|
+
if (o) try {
|
|
767
|
+
return I(JSON.parse(o[1]));
|
|
768
|
+
} catch {}
|
|
769
|
+
if (a.startsWith("{") || a.startsWith("[")) try {
|
|
770
|
+
return I(JSON.parse(a));
|
|
771
|
+
} catch {}
|
|
772
|
+
return /^-?\d+(\.\d+)?$/.test(a) ? String(Number(a)) : a;
|
|
773
|
+
}, ot = (e, t) => {
|
|
774
|
+
let n = at(e), r = at(t);
|
|
775
|
+
if (n === r) return !0;
|
|
776
|
+
let i = (e) => e === "1" || e === "true" ? "__BOOL_TRUE__" : e === "0" || e === "false" ? "__BOOL_FALSE__" : e;
|
|
777
|
+
return i(n) === i(r);
|
|
778
|
+
}, L = (e) => {
|
|
779
|
+
if (!e) return null;
|
|
780
|
+
let t = (e) => e === "restrict" ? "noAction" : e;
|
|
781
|
+
return {
|
|
782
|
+
table: e.table,
|
|
783
|
+
column: e.column,
|
|
784
|
+
onDelete: t(e.onDelete),
|
|
785
|
+
onUpdate: t(e.onUpdate)
|
|
786
|
+
};
|
|
787
|
+
}, st = (e, t) => {
|
|
788
|
+
let n = e && e.length > 0 ? e : void 0, r = t && t.length > 0 ? t : void 0;
|
|
789
|
+
if (!n && !r) return !0;
|
|
790
|
+
if (!n || !r || n.length !== r.length) return !1;
|
|
791
|
+
let i = [...n].sort(), a = [...r].sort();
|
|
792
|
+
return i.every((e, t) => e === a[t]);
|
|
793
|
+
}, ct = (e, t) => e.type === t.type && e.nullable === t.nullable && e.unique === t.unique && ot(e, t) && JSON.stringify(L(e.references)) === JSON.stringify(L(t.references)) && st(e.oneOf, t.oneOf), lt = (e, t) => t.oneOf && t.oneOf.length > 0 ? R({
|
|
794
|
+
kind: "add-check",
|
|
795
|
+
table: e,
|
|
796
|
+
column: t.name,
|
|
797
|
+
values: t.oneOf
|
|
798
|
+
}, "safe", `enum-membership CHECK on ${e}.${t.name}`) : void 0, ut = (e, t) => e.unique === t.unique && (e.expression || t.expression ? !0 : e.columns.length === t.columns.length && e.columns.every((e, n) => e === t.columns[n])), dt = (e, t) => {
|
|
799
|
+
let n = new Map(t.columns.map((e) => [e.name, e])), r = new Map(e.columns.map((e) => [e.name, e])), i = [], a = [], o = [];
|
|
800
|
+
for (let t of e.columns) {
|
|
801
|
+
let e = n.get(t.name);
|
|
802
|
+
if (!e) {
|
|
803
|
+
i.push(t);
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
ct(t, e) || o.push({
|
|
807
|
+
declared: t,
|
|
808
|
+
live: e
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
for (let e of t.columns) r.has(e.name) || a.push(e);
|
|
812
|
+
return {
|
|
813
|
+
added: i,
|
|
814
|
+
removed: a,
|
|
815
|
+
changed: o
|
|
816
|
+
};
|
|
817
|
+
}, ft = (e, t) => {
|
|
818
|
+
let n = new Map(t.indexes.map((e) => [e.name, e])), r = new Map(e.indexes.map((e) => [e.name, e])), i = [], a = [];
|
|
819
|
+
for (let t of e.indexes) {
|
|
820
|
+
let e = n.get(t.name);
|
|
821
|
+
(!e || !ut(t, e)) && i.push(t);
|
|
822
|
+
}
|
|
823
|
+
for (let e of t.indexes) r.has(e.name) || a.push(e);
|
|
824
|
+
return {
|
|
825
|
+
added: i,
|
|
826
|
+
removed: a
|
|
827
|
+
};
|
|
828
|
+
}, R = (e, t, n, r) => ({
|
|
829
|
+
op: e,
|
|
830
|
+
classification: t,
|
|
831
|
+
atomicByDialect: nt(e.kind),
|
|
832
|
+
...n ? { reason: n } : {},
|
|
833
|
+
...r ? { blocked: r } : {}
|
|
834
|
+
}), pt = (e, t, n) => {
|
|
835
|
+
let r = {
|
|
836
|
+
kind: "add-column",
|
|
837
|
+
table: e,
|
|
838
|
+
column: t
|
|
839
|
+
};
|
|
840
|
+
return t.nullable ? R(r, "safe", "nullable column add — no backfill needed") : t.hasDefault && t.defaultValue !== void 0 ? R(r, "needs-default", "NOT NULL with literal default — DDL DEFAULT will populate existing rows") : n?.backfill ? R(r, "needs-backfill", `backfill declared (kind=${n.backfill.kind}) — applier will run 3-step add/update/set-not-null`) : R(r, "needs-backfill", "NOT NULL column on a table whose row count is unknown", { fix: `declare \`${t.name}: <type>().backfill(sql\`...\`)\` OR \`.default(value)\` so existing rows survive the migration` });
|
|
841
|
+
}, mt = (e, t, n) => {
|
|
842
|
+
let r = {
|
|
843
|
+
kind: "drop-column",
|
|
844
|
+
table: e,
|
|
845
|
+
column: t.name
|
|
846
|
+
};
|
|
847
|
+
return n?.dropped ? R(r, "lossy", `column dropped via \`${t.name}: dropped()\` marker — intentional`) : R(r, "lossy", "column missing from declared schema", { fix: `if intentional, add \`${t.name}: dropped()\` to the schema. If a typo, restore the field` });
|
|
848
|
+
}, ht = (e, t, n, r) => R({
|
|
849
|
+
kind: "create-table",
|
|
850
|
+
table: e,
|
|
851
|
+
columns: t,
|
|
852
|
+
indexes: n,
|
|
853
|
+
...r && r.length > 0 ? { primaryKey: r } : {}
|
|
854
|
+
}, "safe", "new table — no data to preserve"), gt = (e) => R({
|
|
855
|
+
kind: "drop-table",
|
|
856
|
+
table: e
|
|
857
|
+
}, "lossy", "table missing from declared schema", { fix: "if intentional, set VOLTRO_DESTRUCTIVE_OK=1 OR add a file-based migration. If a typo, restore the table declaration" }), _t = 5e4, vt = (e, t, n, r) => {
|
|
858
|
+
let i = (n ?? 0) >= r, a;
|
|
859
|
+
return a = t.unique ? t.columns.length === 1 ? {
|
|
860
|
+
kind: "add-unique",
|
|
861
|
+
table: e,
|
|
862
|
+
column: t.columns[0]
|
|
863
|
+
} : {
|
|
864
|
+
kind: "add-unique-composite",
|
|
865
|
+
table: e,
|
|
866
|
+
name: t.name,
|
|
867
|
+
fields: t.columns
|
|
868
|
+
} : {
|
|
869
|
+
kind: "add-index",
|
|
870
|
+
table: e,
|
|
871
|
+
index: t
|
|
872
|
+
}, i ? R(a, "online-required", `online build on ${n} rows — CONCURRENTLY / INPLACE / WITH(ONLINE=ON) depending on dialect`) : R(a, "safe", t.unique ? t.columns.length === 1 ? "add unique constraint" : "add composite UNIQUE constraint" : "add B-tree index");
|
|
873
|
+
}, yt = (e, t) => t.unique && t.columns.length > 1 ? R({
|
|
874
|
+
kind: "drop-unique-composite",
|
|
875
|
+
table: e,
|
|
876
|
+
name: t.name
|
|
877
|
+
}, "safe", "drop composite UNIQUE constraint (no data loss — constraint is derived metadata)") : R({
|
|
878
|
+
kind: "drop-index",
|
|
879
|
+
table: e,
|
|
880
|
+
index: t.name
|
|
881
|
+
}, "safe", "drop index (no data loss — index is derived data)"), bt = (e, t) => {
|
|
882
|
+
if (e === "reference") return "text";
|
|
883
|
+
switch (t) {
|
|
884
|
+
case "sqlite":
|
|
885
|
+
case "turso": return e === "timestamp" || e === "date" || e === "json" ? "text" : e === "boolean" ? "integer" : e === "decimal" ? "text" : e === "bigint" ? "integer" : e;
|
|
886
|
+
case "mssql": return e === "json" ? "text" : e;
|
|
887
|
+
default: return e;
|
|
888
|
+
}
|
|
889
|
+
}, xt = (e, t, n, r, i, a) => {
|
|
890
|
+
let o = [];
|
|
891
|
+
if (t.nullable !== n.nullable) {
|
|
892
|
+
let n = {
|
|
893
|
+
kind: "alter-column-nullability",
|
|
894
|
+
table: e,
|
|
895
|
+
column: t.name,
|
|
896
|
+
toNullable: t.nullable
|
|
897
|
+
};
|
|
898
|
+
o.push(t.nullable ? R(n, "safe", `loosen ${e}.${t.name} to nullable`) : R(n, "needs-backfill", `tighten ${e}.${t.name} to NOT NULL — existing rows must already be non-null`));
|
|
899
|
+
}
|
|
900
|
+
if (t.unique !== n.unique) if (t.unique) {
|
|
901
|
+
let n = {
|
|
902
|
+
kind: "add-unique",
|
|
903
|
+
table: e,
|
|
904
|
+
column: t.name
|
|
905
|
+
}, r = (i ?? 0) >= a;
|
|
906
|
+
o.push(r ? R(n, "online-required", `online unique build on ${i} rows — CONCURRENTLY / INPLACE / WITH(ONLINE=ON) depending on dialect`) : R(n, "safe", `add unique constraint on ${e}.${t.name} — existing rows must already be distinct (else the DB rejects at migrate)`));
|
|
907
|
+
} else o.push(R({
|
|
908
|
+
kind: "drop-unique",
|
|
909
|
+
table: e,
|
|
910
|
+
column: t.name
|
|
911
|
+
}, "safe", `drop unique constraint on ${e}.${t.name} — constraint is derived metadata, no data loss`));
|
|
912
|
+
if (bt(t.type, r) !== bt(n.type, r)) {
|
|
913
|
+
let r = t.narrowedFrom;
|
|
914
|
+
if (r && r.from === n.type) {
|
|
915
|
+
let i = {
|
|
916
|
+
kind: "alter-column-type",
|
|
917
|
+
table: e,
|
|
918
|
+
column: t.name,
|
|
919
|
+
from: n.type,
|
|
920
|
+
to: t.type,
|
|
921
|
+
...r.using === void 0 ? {} : { using: r.using }
|
|
922
|
+
};
|
|
923
|
+
o.push(R(i, "needs-backfill", `type change ${n.type} → ${t.type} on ${e}.${t.name} — narrowedFrom('${r.from}') declared` + (r.using ? ` with USING ${r.using}` : " (implicit cast)")));
|
|
924
|
+
} else {
|
|
925
|
+
let r = {
|
|
926
|
+
kind: "alter-column-type",
|
|
927
|
+
table: e,
|
|
928
|
+
column: t.name,
|
|
929
|
+
from: n.type,
|
|
930
|
+
to: t.type
|
|
931
|
+
};
|
|
932
|
+
o.push(R(r, "lossy", `type change ${n.type} → ${t.type} on ${e}.${t.name} may not be value-preserving`, { fix: `acknowledge it on the column: \`.narrowedFrom('${n.type}', { using: '${t.name}::${t.type}' })\` (drop \`using\` if the cast is implicit), or use a file-based migration` }));
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (!ot(t, n)) {
|
|
936
|
+
let n = {
|
|
937
|
+
kind: "alter-column-default",
|
|
938
|
+
table: e,
|
|
939
|
+
column: t.name,
|
|
940
|
+
hasDefault: t.hasDefault,
|
|
941
|
+
...t.defaultValue === void 0 ? {} : { defaultValue: t.defaultValue }
|
|
942
|
+
};
|
|
943
|
+
o.push(R(n, "safe", `default change on ${e}.${t.name} affects only future inserts`));
|
|
944
|
+
}
|
|
945
|
+
if (!st(t.oneOf, n.oneOf)) {
|
|
946
|
+
n.oneOf && n.oneOf.length > 0 && o.push(R({
|
|
947
|
+
kind: "drop-check",
|
|
948
|
+
table: e,
|
|
949
|
+
column: t.name
|
|
950
|
+
}, "safe", `drop stale enum CHECK on ${e}.${t.name}`));
|
|
951
|
+
let r = lt(e, t);
|
|
952
|
+
r && o.push(r);
|
|
953
|
+
}
|
|
954
|
+
if (JSON.stringify(L(t.references)) !== JSON.stringify(L(n.references)) && (n.references && o.push(R({
|
|
955
|
+
kind: "drop-foreign-key",
|
|
956
|
+
table: e,
|
|
957
|
+
column: t.name
|
|
958
|
+
}, "safe", `drop FK on ${e}.${t.name} (→ ${n.references.table})`)), t.references)) {
|
|
959
|
+
let n = t.references.table, r = t.references.orphanPolicy ?? "fail", i = {
|
|
960
|
+
kind: "add-foreign-key",
|
|
961
|
+
table: e,
|
|
962
|
+
column: t.name,
|
|
963
|
+
targetTable: n,
|
|
964
|
+
targetColumn: t.references.column,
|
|
965
|
+
...t.references.onDelete ? { onDelete: t.references.onDelete } : {},
|
|
966
|
+
...t.references.onUpdate ? { onUpdate: t.references.onUpdate } : {}
|
|
967
|
+
};
|
|
968
|
+
r === "null" && !t.nullable ? o.push(R(i, "lossy", `add FK ${e}.${t.name} → ${n}: orphanPolicy 'null' needs a nullable column (the NULL-out would violate NOT NULL)`, { fix: `make ${t.name} \`.nullable()\`, OR use \`orphanPolicy: 'delete'\`, OR clean the orphans and drop the policy` })) : r === "null" || r === "delete" ? o.push(R({
|
|
969
|
+
...i,
|
|
970
|
+
orphanPolicy: r
|
|
971
|
+
}, "lossy", r === "delete" ? `add FK ${e}.${t.name} → ${n}: orphanPolicy 'delete' first DELETES rows whose ${t.name} points at a missing ${n}` : `add FK ${e}.${t.name} → ${n}: orphanPolicy 'null' first NULLs ${t.name} where it points at a missing ${n}`)) : o.push(R(i, "needs-backfill", `add FK ${e}.${t.name} → ${n} — existing rows must already reference a valid ${n} (else set orphanPolicy:'null'/'delete')`));
|
|
972
|
+
}
|
|
973
|
+
return o;
|
|
974
|
+
}, St = (e, t, n) => {
|
|
975
|
+
let r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Set(), o = new Set(n.removed.map((e) => e.name)), s = new Set(n.added.map((e) => e.name));
|
|
976
|
+
for (let [n, c] of Object.entries(t)) {
|
|
977
|
+
let t = c.renamedFrom;
|
|
978
|
+
t && o.has(t) && s.has(n) && (i.add(n), a.add(t), r.push(R({
|
|
979
|
+
kind: "rename-column",
|
|
980
|
+
table: e.tableName,
|
|
981
|
+
from: t,
|
|
982
|
+
to: n
|
|
983
|
+
}, "safe", `rename via \`.renamedFrom('${t}')\``)));
|
|
984
|
+
}
|
|
985
|
+
return {
|
|
986
|
+
ops: r,
|
|
987
|
+
consumedAddedNames: i,
|
|
988
|
+
consumedRemovedNames: a
|
|
989
|
+
};
|
|
990
|
+
}, Ct = (e) => {
|
|
991
|
+
let t = {
|
|
992
|
+
safe: 0,
|
|
993
|
+
needsDefault: 0,
|
|
994
|
+
needsBackfill: 0,
|
|
995
|
+
needsRenameAnnotation: 0,
|
|
996
|
+
lossy: 0,
|
|
997
|
+
onlineRequired: 0,
|
|
998
|
+
multiStep: 0,
|
|
999
|
+
blocked: 0
|
|
1000
|
+
};
|
|
1001
|
+
for (let n of e) switch (n.blocked && t.blocked++, n.classification) {
|
|
1002
|
+
case "safe":
|
|
1003
|
+
t.safe++;
|
|
1004
|
+
break;
|
|
1005
|
+
case "needs-default":
|
|
1006
|
+
t.needsDefault++;
|
|
1007
|
+
break;
|
|
1008
|
+
case "needs-backfill":
|
|
1009
|
+
t.needsBackfill++;
|
|
1010
|
+
break;
|
|
1011
|
+
case "needs-rename-annotation":
|
|
1012
|
+
t.needsRenameAnnotation++;
|
|
1013
|
+
break;
|
|
1014
|
+
case "lossy":
|
|
1015
|
+
t.lossy++;
|
|
1016
|
+
break;
|
|
1017
|
+
case "online-required":
|
|
1018
|
+
t.onlineRequired++;
|
|
1019
|
+
break;
|
|
1020
|
+
case "multi-step":
|
|
1021
|
+
t.multiStep++;
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
return t;
|
|
1025
|
+
}, wt = (e) => {
|
|
1026
|
+
let t = F(e.declared), n = rt(e.declared, t), r = new Map(e.live.tables.map((e) => [e.name, e])), i = e.onlineAfter ?? _t, a = [];
|
|
1027
|
+
for (let e of t.tables) if (!r.has(e.name)) {
|
|
1028
|
+
a.push(ht(e.name, e.columns, e.indexes, e.primaryKey));
|
|
1029
|
+
for (let t of e.columns) {
|
|
1030
|
+
let n = lt(e.name, t);
|
|
1031
|
+
n && a.push(n);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
let o = new Set(e.ignoreTables ?? []);
|
|
1035
|
+
for (let t of e.live.tables) n.has(t.name) || $e(t.name) || o.has(t.name) || a.push(gt(t.name));
|
|
1036
|
+
for (let o of t.tables) {
|
|
1037
|
+
let t = r.get(o.name);
|
|
1038
|
+
if (!t) continue;
|
|
1039
|
+
let s = n.get(o.name), c = dt(o, t), l = ft(o, t), u = St(s.table, s.columnDefs, c);
|
|
1040
|
+
a.push(...u.ops);
|
|
1041
|
+
for (let e of c.removed) {
|
|
1042
|
+
if (u.consumedRemovedNames.has(e.name)) continue;
|
|
1043
|
+
let t = s.columnDefs[e.name];
|
|
1044
|
+
a.push(mt(o.name, e, t));
|
|
1045
|
+
}
|
|
1046
|
+
for (let e of c.added) {
|
|
1047
|
+
if (u.consumedAddedNames.has(e.name)) continue;
|
|
1048
|
+
let t = s.columnDefs[e.name];
|
|
1049
|
+
a.push(pt(o.name, e, t));
|
|
1050
|
+
let n = lt(o.name, e);
|
|
1051
|
+
n && a.push(n);
|
|
1052
|
+
}
|
|
1053
|
+
for (let e of l.added) a.push(vt(o.name, e, t.rowCount, i));
|
|
1054
|
+
for (let e of l.removed) a.push(yt(o.name, e));
|
|
1055
|
+
for (let { declared: n, live: r } of c.changed) a.push(...xt(o.name, n, r, e.dialect, t.rowCount, i));
|
|
1056
|
+
}
|
|
1057
|
+
let s = new Set(a.flatMap((e) => e.op.kind === "create-table" ? [e.op.table] : [])), c = new Map(t.tables.map((e) => [e.name, e])), l = (e) => {
|
|
1058
|
+
let t = c.get(e);
|
|
1059
|
+
return t ? t.columns.flatMap((e) => e.references && s.has(e.references.table) ? [{
|
|
1060
|
+
column: e.name,
|
|
1061
|
+
target: e.references.table
|
|
1062
|
+
}] : []) : [];
|
|
1063
|
+
}, u = y([...s], l), d = [];
|
|
1064
|
+
for (let e of t.tables) if (s.has(e.name)) for (let t of e.columns) !t.references || !u.has(v(e.name, t.name)) || d.push(R({
|
|
1065
|
+
kind: "add-foreign-key",
|
|
1066
|
+
table: e.name,
|
|
1067
|
+
column: t.name,
|
|
1068
|
+
targetTable: t.references.table,
|
|
1069
|
+
targetColumn: t.references.column,
|
|
1070
|
+
...t.references.onDelete ? { onDelete: t.references.onDelete } : {},
|
|
1071
|
+
...t.references.onUpdate ? { onUpdate: t.references.onUpdate } : {}
|
|
1072
|
+
}, "safe", `deferred FK ${e.name}.${t.name} → ${t.references.table} — breaks a create-time table cycle`));
|
|
1073
|
+
if (u.size > 0) {
|
|
1074
|
+
let e = (e) => e.references ? Object.fromEntries(Object.entries(e).filter(([e]) => e !== "references")) : e;
|
|
1075
|
+
for (let t = 0; t < a.length; t += 1) {
|
|
1076
|
+
let n = a[t];
|
|
1077
|
+
if (n.op.kind !== "create-table") continue;
|
|
1078
|
+
let r = n.op;
|
|
1079
|
+
if (!r.columns.some((e) => u.has(v(r.table, e.name)))) continue;
|
|
1080
|
+
let i = r.columns.map((t) => u.has(v(r.table, t.name)) ? e(t) : t);
|
|
1081
|
+
a[t] = {
|
|
1082
|
+
...n,
|
|
1083
|
+
op: {
|
|
1084
|
+
...r,
|
|
1085
|
+
columns: i
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
a.push(...d);
|
|
1090
|
+
}
|
|
1091
|
+
let f = new Map(t.tables.map((e) => [e.name, e])), p = /* @__PURE__ */ new Map(), m = (e, t = /* @__PURE__ */ new Set()) => {
|
|
1092
|
+
let n = p.get(e);
|
|
1093
|
+
if (n !== void 0) return n;
|
|
1094
|
+
if (t.has(e)) return 0;
|
|
1095
|
+
let r = f.get(e);
|
|
1096
|
+
if (!r) return 0;
|
|
1097
|
+
let i = new Set(t).add(e), a = 0;
|
|
1098
|
+
for (let t of r.columns) {
|
|
1099
|
+
let n = t.references?.table;
|
|
1100
|
+
n && n !== e && !u.has(v(e, t.name)) && (a = Math.max(a, m(n, i) + 1));
|
|
1101
|
+
}
|
|
1102
|
+
return p.set(e, a), a;
|
|
1103
|
+
}, h = (e) => e === "create-table" ? 0 : e === "drop-table" ? 2 : 1, g = (e) => {
|
|
1104
|
+
let t = "table" in e ? e.table : "";
|
|
1105
|
+
return e.kind === "create-table" ? m(t) : e.kind === "drop-table" ? -m(t) : 0;
|
|
1106
|
+
}, ee = {
|
|
1107
|
+
"add-column": 0,
|
|
1108
|
+
"rename-column": 1,
|
|
1109
|
+
"alter-column-nullability": 2,
|
|
1110
|
+
"alter-column-type": 2,
|
|
1111
|
+
"alter-column-default": 2,
|
|
1112
|
+
"drop-index": 3,
|
|
1113
|
+
"drop-check": 3,
|
|
1114
|
+
"drop-unique": 3,
|
|
1115
|
+
"drop-unique-composite": 3,
|
|
1116
|
+
"drop-foreign-key": 3,
|
|
1117
|
+
"drop-column": 4,
|
|
1118
|
+
"add-index": 5,
|
|
1119
|
+
"add-unique": 5,
|
|
1120
|
+
"add-unique-composite": 5,
|
|
1121
|
+
"add-foreign-key": 5,
|
|
1122
|
+
"add-check": 5
|
|
1123
|
+
}, _ = (e) => ee[e] ?? 9, b = [...a].sort((e, t) => {
|
|
1124
|
+
let n = h(e.op.kind), r = h(t.op.kind);
|
|
1125
|
+
if (n !== r) return n - r;
|
|
1126
|
+
let i = g(e.op), a = g(t.op);
|
|
1127
|
+
if (i !== a) return i - a;
|
|
1128
|
+
let o = "table" in e.op ? e.op.table : "", s = "table" in t.op ? t.op.table : "";
|
|
1129
|
+
if (o !== s) return o.localeCompare(s);
|
|
1130
|
+
let c = _(e.op.kind), l = _(t.op.kind);
|
|
1131
|
+
return c === l ? e.op.kind.localeCompare(t.op.kind) : c - l;
|
|
1132
|
+
});
|
|
1133
|
+
return {
|
|
1134
|
+
operations: b,
|
|
1135
|
+
fromFingerprint: N(e.live),
|
|
1136
|
+
toFingerprint: N(t),
|
|
1137
|
+
summary: Ct(b)
|
|
1138
|
+
};
|
|
1139
|
+
}, Tt = (e) => {
|
|
1140
|
+
let t = /[`"[]?([A-Za-z_][A-Za-z0-9_]*)[`"\]]?\s*(?:in\b|=)/i.exec(e);
|
|
1141
|
+
if (!t) return null;
|
|
1142
|
+
let n = [...e.matchAll(/'((?:[^']|'')*)'/g)].map((e) => e[1].replace(/''/g, "'"));
|
|
1143
|
+
return n.length === 0 ? null : {
|
|
1144
|
+
column: t[1],
|
|
1145
|
+
values: n
|
|
1146
|
+
};
|
|
1147
|
+
}, Et = (e, t) => {
|
|
1148
|
+
if (t === "vector") return "vector";
|
|
1149
|
+
switch (e) {
|
|
1150
|
+
case "text":
|
|
1151
|
+
case "character varying":
|
|
1152
|
+
case "varchar":
|
|
1153
|
+
case "uuid":
|
|
1154
|
+
case "character": return "text";
|
|
1155
|
+
case "integer":
|
|
1156
|
+
case "smallint": return "integer";
|
|
1157
|
+
case "bigint": return "bigint";
|
|
1158
|
+
case "double precision":
|
|
1159
|
+
case "real": return "real";
|
|
1160
|
+
case "numeric":
|
|
1161
|
+
case "decimal": return "decimal";
|
|
1162
|
+
case "boolean": return "boolean";
|
|
1163
|
+
case "bytea": return "bytes";
|
|
1164
|
+
case "timestamp without time zone":
|
|
1165
|
+
case "timestamp with time zone":
|
|
1166
|
+
case "timestamptz":
|
|
1167
|
+
case "timestamp": return "timestamp";
|
|
1168
|
+
case "date": return "date";
|
|
1169
|
+
case "json":
|
|
1170
|
+
case "jsonb": return "json";
|
|
1171
|
+
case "USER-DEFINED": return "text";
|
|
1172
|
+
default: return "text";
|
|
1173
|
+
}
|
|
1174
|
+
}, z = (e) => {
|
|
1175
|
+
switch (e.toUpperCase()) {
|
|
1176
|
+
case "CASCADE": return "cascade";
|
|
1177
|
+
case "RESTRICT": return "restrict";
|
|
1178
|
+
case "SET NULL": return "setNull";
|
|
1179
|
+
case "SET DEFAULT": return "noAction";
|
|
1180
|
+
default: return "noAction";
|
|
1181
|
+
}
|
|
1182
|
+
}, Dt = (e, t) => {
|
|
1183
|
+
let n = e !== void 0 && e !== "" ? Number(e) : NaN;
|
|
1184
|
+
return Number.isInteger(n) && n > 0 ? n : t;
|
|
1185
|
+
}, Ot = () => Dt(process.env.VOLTRO_INTROSPECT_BATCH, 20), kt = 300, At = () => {
|
|
1186
|
+
let e = process.env.VOLTRO_INTROSPECT_TIMEOUT_MS;
|
|
1187
|
+
return e !== void 0 && e.trim() === "0" ? 0 : Dt(e, 3e4);
|
|
1188
|
+
}, jt = (e, t) => {
|
|
1189
|
+
let n = Number.isFinite(t) && t >= 1 ? Math.floor(t) : 1, r = [];
|
|
1190
|
+
for (let t = 0; t < e.length; t += n) r.push(e.slice(t, t + n));
|
|
1191
|
+
return r;
|
|
1192
|
+
}, Mt = (e) => {
|
|
1193
|
+
let t = At(), n = d.gen(function* () {
|
|
1194
|
+
t > 0 && (yield* e.unsafe(`SET LOCAL statement_timeout = ${t}`));
|
|
1195
|
+
let n = (t) => e.unsafe("(" + t.map((e) => `'${e.replace(/'/g, "''")}'`).join(", ") + ")"), r = (t) => e.unsafe(String(Math.max(0, Math.trunc(t)))), i = Ot(), a = (e, t) => d.gen(function* () {
|
|
1196
|
+
let r = [];
|
|
1197
|
+
for (let a of jt(e, i)) a.length !== 0 && r.push(...yield* t(n(a)));
|
|
1198
|
+
return r;
|
|
1199
|
+
}), o = [];
|
|
1200
|
+
for (let t = 0;; t += kt) {
|
|
1201
|
+
let n = yield* e`
|
|
1202
|
+
SELECT c.relname AS table_name,
|
|
1203
|
+
COALESCE(c.reltuples::bigint, 0) AS row_count
|
|
1204
|
+
FROM pg_class c
|
|
1205
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1206
|
+
WHERE n.nspname = current_schema() AND c.relkind = 'r'
|
|
1207
|
+
ORDER BY c.relname
|
|
1208
|
+
LIMIT ${r(kt)} OFFSET ${r(t)}
|
|
1209
|
+
`;
|
|
1210
|
+
if (o.push(...n), n.length < kt) break;
|
|
1211
|
+
}
|
|
1212
|
+
let s = /* @__PURE__ */ new Set(), c = [];
|
|
1213
|
+
for (let e of o) s.has(e.table_name) || (s.add(e.table_name), c.push(e));
|
|
1214
|
+
let l = c.map((e) => e.table_name), u = yield* a(l, (t) => e`
|
|
1215
|
+
SELECT table_name, column_name, data_type, udt_name, is_nullable, column_default
|
|
1216
|
+
FROM information_schema.columns
|
|
1217
|
+
WHERE table_schema = current_schema() AND table_name IN ${t}
|
|
1218
|
+
ORDER BY table_name, ordinal_position
|
|
1219
|
+
`), f = yield* a(l, (t) => e`
|
|
1220
|
+
SELECT rel.relname AS table_name, att.attname AS column_name
|
|
1221
|
+
FROM pg_constraint con
|
|
1222
|
+
JOIN pg_class rel ON rel.oid = con.conrelid
|
|
1223
|
+
JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
|
|
1224
|
+
JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = ANY (con.conkey)
|
|
1225
|
+
WHERE con.contype = 'p' AND nsp.nspname = current_schema()
|
|
1226
|
+
AND rel.relname IN ${t}
|
|
1227
|
+
`), p = yield* a(l, (t) => e`
|
|
1228
|
+
SELECT rel.relname AS table_name, con.conname AS constraint_name,
|
|
1229
|
+
att.attname AS column_name, array_position(con.conkey, att.attnum) AS position
|
|
1230
|
+
FROM pg_constraint con
|
|
1231
|
+
JOIN pg_class rel ON rel.oid = con.conrelid
|
|
1232
|
+
JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
|
|
1233
|
+
JOIN pg_attribute att ON att.attrelid = rel.oid AND att.attnum = ANY (con.conkey)
|
|
1234
|
+
WHERE con.contype = 'u' AND nsp.nspname = current_schema() AND rel.relname IN ${t}
|
|
1235
|
+
`), m = yield* a(l, (t) => e`
|
|
1236
|
+
SELECT rel.relname AS table_name, att.attname AS column_name, pg_get_constraintdef(con.oid) AS def
|
|
1237
|
+
FROM pg_constraint con
|
|
1238
|
+
JOIN pg_class rel ON rel.oid = con.conrelid
|
|
1239
|
+
JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
|
|
1240
|
+
JOIN pg_attribute att ON att.attrelid = rel.oid AND att.attnum = con.conkey[1]
|
|
1241
|
+
WHERE con.contype = 'c' AND nsp.nspname = current_schema() AND array_length(con.conkey, 1) = 1
|
|
1242
|
+
AND rel.relname IN ${t}
|
|
1243
|
+
`), h = /* @__PURE__ */ new Map();
|
|
1244
|
+
for (let e of m) {
|
|
1245
|
+
let t = [...e.def.matchAll(/'((?:[^']|'')*)'/g)].map((e) => e[1].replace(/''/g, "'"));
|
|
1246
|
+
t.length > 0 && h.set(`${e.table_name}.${e.column_name}`, t);
|
|
1247
|
+
}
|
|
1248
|
+
let g = yield* a(l, (t) => e`
|
|
1249
|
+
SELECT
|
|
1250
|
+
rel.relname AS source_table,
|
|
1251
|
+
att.attname AS source_column,
|
|
1252
|
+
frel.relname AS target_table,
|
|
1253
|
+
fatt.attname AS target_column,
|
|
1254
|
+
CASE con.confdeltype
|
|
1255
|
+
WHEN 'c' THEN 'CASCADE' WHEN 'r' THEN 'RESTRICT'
|
|
1256
|
+
WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT'
|
|
1257
|
+
ELSE 'NO ACTION' END AS delete_rule,
|
|
1258
|
+
CASE con.confupdtype
|
|
1259
|
+
WHEN 'c' THEN 'CASCADE' WHEN 'r' THEN 'RESTRICT'
|
|
1260
|
+
WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT'
|
|
1261
|
+
ELSE 'NO ACTION' END AS update_rule
|
|
1262
|
+
FROM pg_constraint con
|
|
1263
|
+
JOIN pg_class rel ON rel.oid = con.conrelid
|
|
1264
|
+
JOIN pg_class frel ON frel.oid = con.confrelid
|
|
1265
|
+
JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
|
|
1266
|
+
JOIN unnest(con.conkey) WITH ORDINALITY AS ck(attnum, ord) ON true
|
|
1267
|
+
JOIN unnest(con.confkey) WITH ORDINALITY AS fk(attnum, ord) ON fk.ord = ck.ord
|
|
1268
|
+
JOIN pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = ck.attnum
|
|
1269
|
+
JOIN pg_attribute fatt ON fatt.attrelid = con.confrelid AND fatt.attnum = fk.attnum
|
|
1270
|
+
WHERE con.contype = 'f' AND nsp.nspname = current_schema()
|
|
1271
|
+
AND rel.relname IN ${t}
|
|
1272
|
+
ORDER BY rel.relname, con.conname, ck.ord
|
|
1273
|
+
`), ee = yield* a(l, (t) => e`
|
|
1274
|
+
WITH ix AS (
|
|
1275
|
+
SELECT
|
|
1276
|
+
c.relname AS table_name,
|
|
1277
|
+
i.relname AS index_name,
|
|
1278
|
+
ix2.indisunique AS is_unique,
|
|
1279
|
+
(0 = ANY (ix2.indkey::int[])) AS has_expression,
|
|
1280
|
+
a.attname AS column_name,
|
|
1281
|
+
array_position(ix2.indkey::int[], a.attnum) + 1 AS position
|
|
1282
|
+
FROM pg_index ix2
|
|
1283
|
+
JOIN pg_class i ON i.oid = ix2.indexrelid
|
|
1284
|
+
JOIN pg_class c ON c.oid = ix2.indrelid
|
|
1285
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
1286
|
+
LEFT JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY (ix2.indkey)
|
|
1287
|
+
LEFT JOIN pg_constraint con ON con.conindid = i.oid
|
|
1288
|
+
WHERE n.nspname = current_schema()
|
|
1289
|
+
AND NOT ix2.indisprimary
|
|
1290
|
+
AND con.conindid IS NULL
|
|
1291
|
+
AND c.relname IN ${t}
|
|
1292
|
+
)
|
|
1293
|
+
SELECT * FROM ix ORDER BY table_name, index_name, position
|
|
1294
|
+
`), _ = /* @__PURE__ */ new Map(), v = /* @__PURE__ */ new Map(), y = /* @__PURE__ */ new Map(), b = /* @__PURE__ */ new Map(), x = /* @__PURE__ */ new Map(), S = /* @__PURE__ */ new Map();
|
|
1295
|
+
for (let e of u) {
|
|
1296
|
+
let t = _.get(e.table_name);
|
|
1297
|
+
t || (t = [], _.set(e.table_name, t)), t.push(e);
|
|
1298
|
+
}
|
|
1299
|
+
for (let e of f) {
|
|
1300
|
+
let t = v.get(e.table_name);
|
|
1301
|
+
t || (t = /* @__PURE__ */ new Set(), v.set(e.table_name, t)), t.add(e.column_name);
|
|
1302
|
+
}
|
|
1303
|
+
let C = /* @__PURE__ */ new Map();
|
|
1304
|
+
for (let e of p) {
|
|
1305
|
+
let t = `${e.table_name}${e.constraint_name}`, n = C.get(t);
|
|
1306
|
+
n || (n = {
|
|
1307
|
+
table: e.table_name,
|
|
1308
|
+
columns: []
|
|
1309
|
+
}, C.set(t, n)), n.columns.push({
|
|
1310
|
+
name: e.column_name,
|
|
1311
|
+
position: e.position
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
for (let [e, t] of C) {
|
|
1315
|
+
let n = [...t.columns].sort((e, t) => e.position - t.position).map((e) => e.name);
|
|
1316
|
+
if (n.length === 1) {
|
|
1317
|
+
let e = y.get(t.table);
|
|
1318
|
+
e || (e = /* @__PURE__ */ new Set(), y.set(t.table, e)), e.add(n[0]);
|
|
1319
|
+
} else {
|
|
1320
|
+
let r = b.get(t.table);
|
|
1321
|
+
r || (r = [], b.set(t.table, r)), r.push({
|
|
1322
|
+
name: e.slice(e.indexOf("\0") + 1),
|
|
1323
|
+
table: t.table,
|
|
1324
|
+
columns: n,
|
|
1325
|
+
unique: !0
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
for (let e of g) {
|
|
1330
|
+
let t = x.get(e.source_table);
|
|
1331
|
+
t || (t = [], x.set(e.source_table, t)), t.push(e);
|
|
1332
|
+
}
|
|
1333
|
+
for (let e of ee) {
|
|
1334
|
+
let t = S.get(e.table_name);
|
|
1335
|
+
t || (t = /* @__PURE__ */ new Map(), S.set(e.table_name, t));
|
|
1336
|
+
let n = t.get(e.index_name);
|
|
1337
|
+
n || (n = [], t.set(e.index_name, n)), n.push(e);
|
|
1338
|
+
}
|
|
1339
|
+
let te = [];
|
|
1340
|
+
for (let e of c) {
|
|
1341
|
+
let t = /* @__PURE__ */ new Set(), n = (_.get(e.table_name) ?? []).filter((e) => t.has(e.column_name) ? !1 : (t.add(e.column_name), !0)), r = v.get(e.table_name) ?? /* @__PURE__ */ new Set(), i = y.get(e.table_name) ?? /* @__PURE__ */ new Set(), a = x.get(e.table_name) ?? [], o = new Map(a.map((e) => [e.source_column, e])), s = new Set(n.filter((e) => e.udt_name === "tsvector").map((e) => e.column_name)), c = n.filter((e) => !s.has(e.column_name)).map((t) => {
|
|
1342
|
+
let n = r.has(t.column_name) && r.size === 1, a = o.get(t.column_name), s = a ? "reference" : n && t.column_name === "id" ? "id" : Et(t.data_type, t.udt_name), c = a ? {
|
|
1343
|
+
table: a.target_table,
|
|
1344
|
+
column: a.target_column,
|
|
1345
|
+
onDelete: z(a.delete_rule),
|
|
1346
|
+
onUpdate: z(a.update_rule)
|
|
1347
|
+
} : void 0, l = h.get(`${e.table_name}.${t.column_name}`);
|
|
1348
|
+
return {
|
|
1349
|
+
name: t.column_name,
|
|
1350
|
+
type: s,
|
|
1351
|
+
nullable: t.is_nullable === "YES",
|
|
1352
|
+
unique: i.has(t.column_name) || n && t.column_name === "id",
|
|
1353
|
+
hasDefault: t.column_default !== null,
|
|
1354
|
+
...t.column_default === null ? {} : { defaultValue: t.column_default },
|
|
1355
|
+
...c ? { references: c } : {},
|
|
1356
|
+
...l ? { oneOf: l } : {}
|
|
1357
|
+
};
|
|
1358
|
+
}), l = [], u = S.get(e.table_name);
|
|
1359
|
+
if (u) for (let [t, n] of u) {
|
|
1360
|
+
let r = /* @__PURE__ */ new Set(), i = [...n].filter((e) => e.column_name !== null && e.position !== null).sort((e, t) => (e.position ?? 0) - (t.position ?? 0)).filter((e) => {
|
|
1361
|
+
let t = e.position;
|
|
1362
|
+
return r.has(t) ? !1 : (r.add(t), !0);
|
|
1363
|
+
});
|
|
1364
|
+
if (i.some((e) => s.has(e.column_name))) continue;
|
|
1365
|
+
let a = n.some((e) => e.has_expression);
|
|
1366
|
+
l.push({
|
|
1367
|
+
name: t,
|
|
1368
|
+
table: e.table_name,
|
|
1369
|
+
columns: i.map((e) => e.column_name),
|
|
1370
|
+
unique: n[0]?.is_unique ?? !1,
|
|
1371
|
+
...a ? { expression: !0 } : {}
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
for (let t of b.get(e.table_name) ?? []) l.push(t);
|
|
1375
|
+
r.size > 0 && l.push({
|
|
1376
|
+
name: `${e.table_name}_pkey`,
|
|
1377
|
+
table: e.table_name,
|
|
1378
|
+
columns: [...r],
|
|
1379
|
+
unique: !0
|
|
1380
|
+
});
|
|
1381
|
+
let d = Number(e.row_count ?? 0);
|
|
1382
|
+
te.push({
|
|
1383
|
+
name: e.table_name,
|
|
1384
|
+
columns: c,
|
|
1385
|
+
indexes: l,
|
|
1386
|
+
...d > 0 ? { rowCount: d } : {}
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
return { tables: te };
|
|
1390
|
+
});
|
|
1391
|
+
return (t > 0 ? e.withTransaction(n) : n).pipe(d.catchAll((e) => {
|
|
1392
|
+
let n = `${String(e)} ${String(e.cause ?? "")}`.toLowerCase();
|
|
1393
|
+
return n.includes("statement timeout") || n.includes("57014") ? d.die(/* @__PURE__ */ Error(`schema introspection exceeded VOLTRO_INTROSPECT_TIMEOUT_MS (${t}ms) — the schema is very large / FK-dense or the database is slow. Raise VOLTRO_INTROSPECT_TIMEOUT_MS, set it to 0 to disable the ceiling, or use DB_DIRECT_URL for a non-pooler connection.`)) : d.fail(e);
|
|
1394
|
+
}));
|
|
1395
|
+
}, Nt = (e, t) => {
|
|
1396
|
+
switch (e.toLowerCase()) {
|
|
1397
|
+
case "varchar":
|
|
1398
|
+
case "char":
|
|
1399
|
+
case "text":
|
|
1400
|
+
case "tinytext":
|
|
1401
|
+
case "mediumtext":
|
|
1402
|
+
case "longtext":
|
|
1403
|
+
case "enum": return "text";
|
|
1404
|
+
case "tinyint": return t.toLowerCase() === "tinyint(1)" ? "boolean" : "integer";
|
|
1405
|
+
case "int":
|
|
1406
|
+
case "integer":
|
|
1407
|
+
case "smallint":
|
|
1408
|
+
case "mediumint": return "integer";
|
|
1409
|
+
case "bigint": return "bigint";
|
|
1410
|
+
case "decimal":
|
|
1411
|
+
case "numeric": return "decimal";
|
|
1412
|
+
case "double":
|
|
1413
|
+
case "float":
|
|
1414
|
+
case "real": return "real";
|
|
1415
|
+
case "datetime":
|
|
1416
|
+
case "timestamp": return "timestamp";
|
|
1417
|
+
case "date": return "date";
|
|
1418
|
+
case "blob":
|
|
1419
|
+
case "tinyblob":
|
|
1420
|
+
case "mediumblob":
|
|
1421
|
+
case "longblob":
|
|
1422
|
+
case "binary":
|
|
1423
|
+
case "varbinary": return "bytes";
|
|
1424
|
+
case "json": return "json";
|
|
1425
|
+
default: return "text";
|
|
1426
|
+
}
|
|
1427
|
+
}, B = (e) => e.map((e) => {
|
|
1428
|
+
let t = e, n = {};
|
|
1429
|
+
for (let e of Object.keys(t)) n[e.toLowerCase()] = t[e];
|
|
1430
|
+
return n;
|
|
1431
|
+
}), Pt = (e) => d.gen(function* () {
|
|
1432
|
+
let t = B(yield* e`
|
|
1433
|
+
SELECT table_name, COALESCE(table_rows, 0) AS table_rows
|
|
1434
|
+
FROM information_schema.tables
|
|
1435
|
+
WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE'
|
|
1436
|
+
ORDER BY table_name`), n = B(yield* e`
|
|
1437
|
+
SELECT table_name, column_name, data_type, column_type, is_nullable, column_default, column_key
|
|
1438
|
+
FROM information_schema.columns
|
|
1439
|
+
WHERE table_schema = DATABASE()
|
|
1440
|
+
ORDER BY table_name, ordinal_position`), r = B(yield* e`
|
|
1441
|
+
SELECT k.table_name AS source_table,
|
|
1442
|
+
k.column_name AS source_column,
|
|
1443
|
+
k.referenced_table_name AS target_table,
|
|
1444
|
+
k.referenced_column_name AS target_column,
|
|
1445
|
+
r.delete_rule, r.update_rule
|
|
1446
|
+
FROM information_schema.key_column_usage k
|
|
1447
|
+
JOIN information_schema.referential_constraints r
|
|
1448
|
+
ON k.constraint_name = r.constraint_name
|
|
1449
|
+
AND k.table_schema = r.constraint_schema
|
|
1450
|
+
WHERE k.table_schema = DATABASE() AND k.referenced_table_name IS NOT NULL`), i = B(yield* e`
|
|
1451
|
+
SELECT table_name, index_name, non_unique, column_name, seq_in_index
|
|
1452
|
+
FROM information_schema.statistics
|
|
1453
|
+
WHERE table_schema = DATABASE() AND index_name <> 'PRIMARY'
|
|
1454
|
+
ORDER BY table_name, index_name, seq_in_index`), a = B(yield* e`
|
|
1455
|
+
SELECT table_name, check_clause
|
|
1456
|
+
FROM information_schema.check_constraints
|
|
1457
|
+
WHERE constraint_schema = DATABASE()`.unprepared.pipe(d.orElseSucceed(() => []))), o = /* @__PURE__ */ new Set(), s = /* @__PURE__ */ new Map();
|
|
1458
|
+
for (let e of a) {
|
|
1459
|
+
let t = /json_valid\(`?([^`)]+)`?\)/i.exec(e.check_clause);
|
|
1460
|
+
if (t) {
|
|
1461
|
+
o.add(`${e.table_name}.${t[1]}`);
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
let n = Tt(e.check_clause);
|
|
1465
|
+
n && s.set(`${e.table_name}.${n.column}`, n.values);
|
|
1466
|
+
}
|
|
1467
|
+
let c = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Map(), u = /* @__PURE__ */ new Map();
|
|
1468
|
+
for (let e of n) {
|
|
1469
|
+
let t = c.get(e.table_name) ?? [];
|
|
1470
|
+
t.push(e), c.set(e.table_name, t);
|
|
1471
|
+
}
|
|
1472
|
+
for (let e of r) {
|
|
1473
|
+
let t = l.get(e.source_table) ?? [];
|
|
1474
|
+
t.push(e), l.set(e.source_table, t);
|
|
1475
|
+
}
|
|
1476
|
+
for (let e of i) {
|
|
1477
|
+
let t = u.get(e.table_name);
|
|
1478
|
+
t || (t = /* @__PURE__ */ new Map(), u.set(e.table_name, t));
|
|
1479
|
+
let n = t.get(e.index_name) ?? [];
|
|
1480
|
+
n.push(e), t.set(e.index_name, n);
|
|
1481
|
+
}
|
|
1482
|
+
let f = [];
|
|
1483
|
+
for (let e of t) {
|
|
1484
|
+
let t = c.get(e.table_name) ?? [], n = l.get(e.table_name) ?? [], r = new Map(n.map((e) => [e.source_column, e])), i = t.filter((e) => e.column_key === "PRI").map((e) => e.column_name), a = /* @__PURE__ */ new Set(), d = u.get(e.table_name);
|
|
1485
|
+
if (d) for (let e of d.values()) e.length === 1 && e[0].non_unique === 0 && a.add(e[0].column_name);
|
|
1486
|
+
let p = t.map((t) => {
|
|
1487
|
+
let n = i.includes(t.column_name) && i.length === 1, c = r.get(t.column_name), l = c ? "reference" : n && t.column_name === "id" ? "id" : o.has(`${e.table_name}.${t.column_name}`) ? "json" : Nt(t.data_type, t.column_type), u = c ? {
|
|
1488
|
+
table: c.target_table,
|
|
1489
|
+
column: c.target_column,
|
|
1490
|
+
onDelete: z(c.delete_rule),
|
|
1491
|
+
onUpdate: z(c.update_rule)
|
|
1492
|
+
} : void 0, d = a.has(t.column_name) || n && t.column_name === "id", f = t.column_default === "NULL" ? null : t.column_default, p = s.get(`${e.table_name}.${t.column_name}`);
|
|
1493
|
+
return {
|
|
1494
|
+
name: t.column_name,
|
|
1495
|
+
type: l,
|
|
1496
|
+
nullable: t.is_nullable === "YES",
|
|
1497
|
+
unique: d,
|
|
1498
|
+
hasDefault: f !== null,
|
|
1499
|
+
...f === null ? {} : { defaultValue: f },
|
|
1500
|
+
...u ? { references: u } : {},
|
|
1501
|
+
...p ? { oneOf: p } : {}
|
|
1502
|
+
};
|
|
1503
|
+
}), m = new Set(n.map((e) => e.source_column)), h = [];
|
|
1504
|
+
if (d) for (let [t, n] of d) {
|
|
1505
|
+
let r = [...n].sort((e, t) => e.seq_in_index - t.seq_in_index);
|
|
1506
|
+
r.length === 1 && r[0].non_unique === 0 || r.length === 1 && r[0].non_unique !== 0 && m.has(r[0].column_name) && t !== `${e.table_name}_${r[0].column_name}_idx` || h.push({
|
|
1507
|
+
name: t,
|
|
1508
|
+
table: e.table_name,
|
|
1509
|
+
columns: r.map((e) => e.column_name),
|
|
1510
|
+
unique: r[0].non_unique === 0
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
i.length > 0 && h.push({
|
|
1514
|
+
name: `${e.table_name}_pkey`,
|
|
1515
|
+
table: e.table_name,
|
|
1516
|
+
columns: i,
|
|
1517
|
+
unique: !0
|
|
1518
|
+
});
|
|
1519
|
+
let g = Number(e.table_rows ?? 0);
|
|
1520
|
+
f.push({
|
|
1521
|
+
name: e.table_name,
|
|
1522
|
+
columns: p,
|
|
1523
|
+
indexes: h,
|
|
1524
|
+
...g > 0 ? { rowCount: g } : {}
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
return { tables: f };
|
|
1528
|
+
}), Ft = (e) => {
|
|
1529
|
+
let t = e.toUpperCase();
|
|
1530
|
+
return t.includes("INT") ? "integer" : t.includes("REAL") || t.includes("FLOA") || t.includes("DOUB") ? "real" : t.includes("BLOB") ? "bytes" : "text";
|
|
1531
|
+
}, It = (e) => d.gen(function* () {
|
|
1532
|
+
let t = yield* e`
|
|
1533
|
+
SELECT name, sql FROM sqlite_master
|
|
1534
|
+
WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
|
|
1535
|
+
ORDER BY name`, n = (e) => `'${e.replace(/'/g, "''")}'`, r = [];
|
|
1536
|
+
for (let { name: i, sql: a } of t) {
|
|
1537
|
+
let t = /* @__PURE__ */ new Map();
|
|
1538
|
+
for (let e of (a ?? "").matchAll(/CHECK\s*\(\s*"([^"]+)"\s+IN\s*\(([^)]*)\)/gi)) {
|
|
1539
|
+
let n = [...e[2].matchAll(/'((?:[^']|'')*)'/g)].map((e) => e[1].replace(/''/g, "'"));
|
|
1540
|
+
n.length > 0 && t.set(e[1], n);
|
|
1541
|
+
}
|
|
1542
|
+
let o = yield* e.unsafe(`PRAGMA table_info(${n(i)})`), s = yield* e.unsafe(`PRAGMA foreign_key_list(${n(i)})`), c = yield* e.unsafe(`PRAGMA index_list(${n(i)})`), l = new Map(s.map((e) => [e.from, e])), u = o.filter((e) => e.pk > 0).sort((e, t) => e.pk - t.pk).map((e) => e.name), d = /* @__PURE__ */ new Map(), f = /* @__PURE__ */ new Set();
|
|
1543
|
+
for (let t of c) {
|
|
1544
|
+
let r = (yield* e.unsafe(`PRAGMA index_info(${n(t.name)})`)).map((e) => e.name).filter((e) => e !== null);
|
|
1545
|
+
d.set(t.name, r), t.unique === 1 && r.length === 1 && f.add(r[0]);
|
|
1546
|
+
}
|
|
1547
|
+
let p = o.map((e) => {
|
|
1548
|
+
let n = u.length === 1 && u[0] === e.name, r = l.get(e.name), i = r ? "reference" : n && e.name === "id" ? "id" : Ft(e.type), a = r ? {
|
|
1549
|
+
table: r.table,
|
|
1550
|
+
column: r.to,
|
|
1551
|
+
onDelete: z(r.on_delete),
|
|
1552
|
+
onUpdate: z(r.on_update)
|
|
1553
|
+
} : void 0, o = t.get(e.name);
|
|
1554
|
+
return {
|
|
1555
|
+
name: e.name,
|
|
1556
|
+
type: i,
|
|
1557
|
+
nullable: e.notnull === 0,
|
|
1558
|
+
unique: f.has(e.name) || n && e.name === "id",
|
|
1559
|
+
hasDefault: e.dflt_value !== null,
|
|
1560
|
+
...e.dflt_value === null ? {} : { defaultValue: e.dflt_value },
|
|
1561
|
+
...a ? { references: a } : {},
|
|
1562
|
+
...o ? { oneOf: o } : {}
|
|
1563
|
+
};
|
|
1564
|
+
}), m = new Set(s.map((e) => e.from)), h = [];
|
|
1565
|
+
for (let e of c) {
|
|
1566
|
+
if (e.origin === "pk") continue;
|
|
1567
|
+
let t = d.get(e.name) ?? [];
|
|
1568
|
+
t.length === 1 && e.unique === 1 || t.length === 1 && e.unique !== 1 && m.has(t[0]) && e.name !== `${i}_${t[0]}_idx` || h.push({
|
|
1569
|
+
name: e.name,
|
|
1570
|
+
table: i,
|
|
1571
|
+
columns: t,
|
|
1572
|
+
unique: e.unique === 1
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
u.length > 0 && h.push({
|
|
1576
|
+
name: `${i}_pkey`,
|
|
1577
|
+
table: i,
|
|
1578
|
+
columns: u,
|
|
1579
|
+
unique: !0
|
|
1580
|
+
}), r.push({
|
|
1581
|
+
name: i,
|
|
1582
|
+
columns: p,
|
|
1583
|
+
indexes: h
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
return { tables: r };
|
|
1587
|
+
}), Lt = (e) => {
|
|
1588
|
+
switch (e.toLowerCase()) {
|
|
1589
|
+
case "int":
|
|
1590
|
+
case "smallint":
|
|
1591
|
+
case "tinyint": return "integer";
|
|
1592
|
+
case "bigint": return "bigint";
|
|
1593
|
+
case "decimal":
|
|
1594
|
+
case "numeric":
|
|
1595
|
+
case "money": return "decimal";
|
|
1596
|
+
case "float":
|
|
1597
|
+
case "real": return "real";
|
|
1598
|
+
case "bit": return "boolean";
|
|
1599
|
+
case "datetime2":
|
|
1600
|
+
case "datetime":
|
|
1601
|
+
case "smalldatetime":
|
|
1602
|
+
case "datetimeoffset": return "timestamp";
|
|
1603
|
+
case "date": return "date";
|
|
1604
|
+
case "varbinary":
|
|
1605
|
+
case "binary":
|
|
1606
|
+
case "image": return "bytes";
|
|
1607
|
+
default: return "text";
|
|
1608
|
+
}
|
|
1609
|
+
}, V = (e) => e === !0 || e === 1, Rt = (e) => d.gen(function* () {
|
|
1610
|
+
let t = yield* e`
|
|
1611
|
+
SELECT TABLE_NAME AS table_name FROM INFORMATION_SCHEMA.TABLES
|
|
1612
|
+
WHERE TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME`, n = yield* e`
|
|
1613
|
+
SELECT TABLE_NAME AS table_name, COLUMN_NAME AS column_name, DATA_TYPE AS data_type,
|
|
1614
|
+
IS_NULLABLE AS is_nullable, COLUMN_DEFAULT AS column_default
|
|
1615
|
+
FROM INFORMATION_SCHEMA.COLUMNS ORDER BY TABLE_NAME, ORDINAL_POSITION`, r = yield* e`
|
|
1616
|
+
SELECT st.name AS source_table, sc.name AS source_column,
|
|
1617
|
+
tt.name AS target_table, tc.name AS target_column,
|
|
1618
|
+
fk.delete_referential_action_desc AS delete_rule,
|
|
1619
|
+
fk.update_referential_action_desc AS update_rule
|
|
1620
|
+
FROM sys.foreign_keys fk
|
|
1621
|
+
JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
|
|
1622
|
+
JOIN sys.tables st ON st.object_id = fkc.parent_object_id
|
|
1623
|
+
JOIN sys.columns sc ON sc.object_id = fkc.parent_object_id AND sc.column_id = fkc.parent_column_id
|
|
1624
|
+
JOIN sys.tables tt ON tt.object_id = fkc.referenced_object_id
|
|
1625
|
+
JOIN sys.columns tc ON tc.object_id = fkc.referenced_object_id AND tc.column_id = fkc.referenced_column_id`, i = yield* e`
|
|
1626
|
+
SELECT t.name AS table_name, i.name AS index_name, i.is_unique AS is_unique,
|
|
1627
|
+
i.is_primary_key AS is_primary, c.name AS column_name, ic.key_ordinal AS key_ordinal
|
|
1628
|
+
FROM sys.indexes i
|
|
1629
|
+
JOIN sys.tables t ON t.object_id = i.object_id
|
|
1630
|
+
JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
|
1631
|
+
JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
|
|
1632
|
+
WHERE i.type > 0 AND ic.is_included_column = 0
|
|
1633
|
+
ORDER BY t.name, i.name, ic.key_ordinal`, a = yield* e`
|
|
1634
|
+
SELECT t.name AS table_name, c.name AS column_name, cc.definition AS def
|
|
1635
|
+
FROM sys.check_constraints cc
|
|
1636
|
+
JOIN sys.tables t ON t.object_id = cc.parent_object_id
|
|
1637
|
+
JOIN sys.columns c ON c.object_id = cc.parent_object_id AND c.column_id = cc.parent_column_id
|
|
1638
|
+
WHERE cc.parent_column_id <> 0`, o = /* @__PURE__ */ new Map();
|
|
1639
|
+
for (let e of a) {
|
|
1640
|
+
let t = [...e.def.matchAll(/'((?:[^']|'')*)'/g)].map((e) => e[1].replace(/''/g, "'"));
|
|
1641
|
+
t.length > 0 && o.set(`${e.table_name}.${e.column_name}`, t);
|
|
1642
|
+
}
|
|
1643
|
+
let s = /* @__PURE__ */ new Map();
|
|
1644
|
+
for (let e of n) {
|
|
1645
|
+
let t = s.get(e.table_name) ?? [];
|
|
1646
|
+
t.push(e), s.set(e.table_name, t);
|
|
1647
|
+
}
|
|
1648
|
+
let c = /* @__PURE__ */ new Map();
|
|
1649
|
+
for (let e of r) {
|
|
1650
|
+
let t = c.get(e.source_table) ?? [];
|
|
1651
|
+
t.push(e), c.set(e.source_table, t);
|
|
1652
|
+
}
|
|
1653
|
+
let l = /* @__PURE__ */ new Map();
|
|
1654
|
+
for (let e of i) {
|
|
1655
|
+
let t = l.get(e.table_name);
|
|
1656
|
+
t || (t = /* @__PURE__ */ new Map(), l.set(e.table_name, t));
|
|
1657
|
+
let n = t.get(e.index_name) ?? [];
|
|
1658
|
+
n.push(e), t.set(e.index_name, n);
|
|
1659
|
+
}
|
|
1660
|
+
let u = [];
|
|
1661
|
+
for (let { table_name: e } of t) {
|
|
1662
|
+
let t = s.get(e) ?? [], n = c.get(e) ?? [], r = new Map(n.map((e) => [e.source_column, e])), i = new Set(n.map((e) => e.source_column)), a = l.get(e), d = [], f = /* @__PURE__ */ new Set();
|
|
1663
|
+
if (a) for (let [, e] of a) {
|
|
1664
|
+
let t = [...e].sort((e, t) => e.key_ordinal - t.key_ordinal);
|
|
1665
|
+
if (V(t[0].is_primary)) for (let e of t) d.push(e.column_name);
|
|
1666
|
+
else V(t[0].is_unique) && t.length === 1 && f.add(t[0].column_name);
|
|
1667
|
+
}
|
|
1668
|
+
let p = t.map((t) => {
|
|
1669
|
+
let n = d.length === 1 && d[0] === t.column_name, i = r.get(t.column_name), a = i ? "reference" : n && t.column_name === "id" ? "id" : Lt(t.data_type), s = i ? {
|
|
1670
|
+
table: i.target_table,
|
|
1671
|
+
column: i.target_column,
|
|
1672
|
+
onDelete: zt(i.delete_rule),
|
|
1673
|
+
onUpdate: zt(i.update_rule)
|
|
1674
|
+
} : void 0, c = t.column_default === null ? null : Bt(t.column_default), l = o.get(`${e}.${t.column_name}`);
|
|
1675
|
+
return {
|
|
1676
|
+
name: t.column_name,
|
|
1677
|
+
type: a,
|
|
1678
|
+
nullable: t.is_nullable === "YES",
|
|
1679
|
+
unique: f.has(t.column_name) || n && t.column_name === "id",
|
|
1680
|
+
hasDefault: c !== null,
|
|
1681
|
+
...c === null ? {} : { defaultValue: c },
|
|
1682
|
+
...s ? { references: s } : {},
|
|
1683
|
+
...l ? { oneOf: l } : {}
|
|
1684
|
+
};
|
|
1685
|
+
}), m = [];
|
|
1686
|
+
if (a) for (let [t, n] of a) {
|
|
1687
|
+
let r = [...n].sort((e, t) => e.key_ordinal - t.key_ordinal);
|
|
1688
|
+
V(r[0].is_primary) || r.length === 1 && V(r[0].is_unique) || r.length === 1 && !V(r[0].is_unique) && i.has(r[0].column_name) && t !== `${e}_${r[0].column_name}_idx` || m.push({
|
|
1689
|
+
name: t,
|
|
1690
|
+
table: e,
|
|
1691
|
+
columns: r.map((e) => e.column_name),
|
|
1692
|
+
unique: V(r[0].is_unique)
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
d.length > 0 && m.push({
|
|
1696
|
+
name: `${e}_pkey`,
|
|
1697
|
+
table: e,
|
|
1698
|
+
columns: d,
|
|
1699
|
+
unique: !0
|
|
1700
|
+
}), u.push({
|
|
1701
|
+
name: e,
|
|
1702
|
+
columns: p,
|
|
1703
|
+
indexes: m
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
return { tables: u };
|
|
1707
|
+
}), zt = (e) => {
|
|
1708
|
+
switch (e.toUpperCase().replace(/_/g, " ")) {
|
|
1709
|
+
case "CASCADE": return "cascade";
|
|
1710
|
+
case "SET NULL": return "setNull";
|
|
1711
|
+
case "SET DEFAULT": return "noAction";
|
|
1712
|
+
default: return "noAction";
|
|
1713
|
+
}
|
|
1714
|
+
}, Bt = (e) => {
|
|
1715
|
+
let t = e.trim();
|
|
1716
|
+
for (; t.startsWith("(") && t.endsWith(")");) t = t.slice(1, -1).trim();
|
|
1717
|
+
return t.startsWith("N'") && t.endsWith("'") ? t = t.slice(2, -1) : t.startsWith("'") && t.endsWith("'") && (t = t.slice(1, -1)), t;
|
|
1718
|
+
}, Vt = (e) => e.onDialectOrElse({
|
|
1719
|
+
mysql: () => Pt(e),
|
|
1720
|
+
mssql: () => Rt(e),
|
|
1721
|
+
sqlite: () => It(e),
|
|
1722
|
+
orElse: () => Mt(e)
|
|
1723
|
+
}), Ht = (e, t) => `${t}(${e.precision ?? 38}, ${e.scale ?? 0})`, H = (e) => {
|
|
1724
|
+
switch (e.type) {
|
|
1725
|
+
case "id": return "text";
|
|
1726
|
+
case "text": return "text";
|
|
1727
|
+
case "integer": return "integer";
|
|
1728
|
+
case "real": return "double precision";
|
|
1729
|
+
case "decimal": return Ht(e, "NUMERIC");
|
|
1730
|
+
case "bigint": return "bigint";
|
|
1731
|
+
case "boolean": return "boolean";
|
|
1732
|
+
case "timestamp": return "timestamptz";
|
|
1733
|
+
case "date": return "date";
|
|
1734
|
+
case "json": return "jsonb";
|
|
1735
|
+
case "bytes": return "bytea";
|
|
1736
|
+
case "reference": return "text";
|
|
1737
|
+
case "vector": return "text";
|
|
1738
|
+
case "enum": return "text";
|
|
1739
|
+
case "array": return "text";
|
|
1740
|
+
case "interval": return "interval";
|
|
1741
|
+
case "raw": return e.rawDdl ?? "text";
|
|
1742
|
+
}
|
|
1743
|
+
}, U = (e) => {
|
|
1744
|
+
switch (e.type) {
|
|
1745
|
+
case "id": return "VARCHAR(64)";
|
|
1746
|
+
case "text": return e.hasDefault && e.defaultValue !== void 0 || e.oneOf && e.oneOf.length > 0 ? "VARCHAR(255)" : "LONGTEXT";
|
|
1747
|
+
case "integer": return "INT";
|
|
1748
|
+
case "real": return "DOUBLE";
|
|
1749
|
+
case "decimal": return Ht(e, "DECIMAL");
|
|
1750
|
+
case "bigint": return "BIGINT";
|
|
1751
|
+
case "boolean": return "TINYINT(1)";
|
|
1752
|
+
case "timestamp": return "DATETIME(6)";
|
|
1753
|
+
case "date": return "DATE";
|
|
1754
|
+
case "json": return "JSON";
|
|
1755
|
+
case "bytes": return "LONGBLOB";
|
|
1756
|
+
case "reference": return "VARCHAR(64)";
|
|
1757
|
+
case "vector": return "BLOB";
|
|
1758
|
+
case "enum": return "TEXT";
|
|
1759
|
+
case "array": return "JSON";
|
|
1760
|
+
case "interval": return "BIGINT";
|
|
1761
|
+
case "raw": return e.rawDdl ?? "LONGTEXT";
|
|
1762
|
+
}
|
|
1763
|
+
}, W = (e) => `"${e}"`, G = (e) => `\`${e.replace(/`/g, "``")}\``, Ut = (e, t) => `ALTER TABLE ${t(e.table)} DROP COLUMN IF EXISTS ${t(e.column)};`, Wt = (e) => `ALTER TABLE ${G(e.table)} DROP COLUMN IF EXISTS ${G(e.column)}, ALGORITHM=COPY;`, Gt = (e, t) => `ALTER TABLE ${t(e.table)} RENAME COLUMN ${t(e.from)} TO ${t(e.to)};`, Kt = (e, t, n) => `ALTER TABLE ${n(e.table)} RENAME COLUMN ${n(e.column)} TO ${n(`${e.column}${mn(t)}`)};`, qt = (e, t, n) => e.hasDefault && e.defaultValue !== void 0 ? `ALTER TABLE ${t(e.table)} ALTER COLUMN ${t(e.column)} SET ${n(e.defaultValue) ?? "DEFAULT NULL"};` : `ALTER TABLE ${t(e.table)} ALTER COLUMN ${t(e.column)} DROP DEFAULT;`, K = (e, t, n) => e.find((e) => e.tableName === t)?.fields[n], Jt = (e, t, n) => {
|
|
1764
|
+
let r = n ?? e.nullable, i = fe(e, "mariadb");
|
|
1765
|
+
return [
|
|
1766
|
+
G(t),
|
|
1767
|
+
O(e, "mariadb"),
|
|
1768
|
+
r ? null : "NOT NULL",
|
|
1769
|
+
i
|
|
1770
|
+
].filter(Boolean).join(" ");
|
|
1771
|
+
}, Yt = (e) => e == null ? null : e === "now" ? "DEFAULT CURRENT_TIMESTAMP(6)" : typeof e == "boolean" ? `DEFAULT ${+!!e}` : typeof e == "number" ? `DEFAULT ${e}` : typeof e == "string" ? /[()]|^'/.test(e) ? `DEFAULT ${e}` : `DEFAULT '${e.replace(/'/g, "''")}'` : typeof e == "object" && !Array.isArray(e) ? k(e, "mysql") : null, Xt = (e) => e == null ? null : e === "now" ? "DEFAULT now()" : typeof e == "boolean" || typeof e == "number" ? `DEFAULT ${e}` : typeof e == "string" ? /[()]|::|^'/.test(e) ? `DEFAULT ${e}` : `DEFAULT '${e.replace(/'/g, "''")}'` : typeof e == "object" && !Array.isArray(e) ? k(e, "postgres") : null, q = (e, t) => {
|
|
1772
|
+
if (!e.generatedAs) return null;
|
|
1773
|
+
let { expr: n, stored: r } = e.generatedAs;
|
|
1774
|
+
return t === "postgres" ? `GENERATED ALWAYS AS (${n}) STORED` : t === "mssql" ? `AS (${n})${r ? " PERSISTED" : ""}` : `GENERATED ALWAYS AS (${n}) ${r ? "STORED" : "VIRTUAL"}`;
|
|
1775
|
+
}, Zt = (e) => {
|
|
1776
|
+
if (e.type === "id" && e.idScheme?.kind === "numeric") return A(e.name, "postgres");
|
|
1777
|
+
let t = q(e, "postgres");
|
|
1778
|
+
if (t) return `${W(e.name)} ${H(e)} ${t}`;
|
|
1779
|
+
let n = [W(e.name), H(e)];
|
|
1780
|
+
if (e.type === "id" && n.push("PRIMARY KEY"), e.nullable || n.push("NOT NULL"), e.unique && e.type !== "id" && n.push("UNIQUE"), e.hasDefault) {
|
|
1781
|
+
let t = Xt(e.defaultValue);
|
|
1782
|
+
t && n.push(t);
|
|
1783
|
+
}
|
|
1784
|
+
return e.references && (n.push(`REFERENCES ${W(e.references.table)} (${W(e.references.column)})`), e.references.onDelete && n.push(`ON DELETE ${{
|
|
1785
|
+
cascade: "CASCADE",
|
|
1786
|
+
restrict: "RESTRICT",
|
|
1787
|
+
setNull: "SET NULL",
|
|
1788
|
+
noAction: "NO ACTION"
|
|
1789
|
+
}[e.references.onDelete]}`)), n.join(" ");
|
|
1790
|
+
}, J = (e, t) => e.primaryKey && e.primaryKey.length > 0 ? `,\n PRIMARY KEY (${e.primaryKey.map(t).join(", ")})` : "", Qt = (e) => {
|
|
1791
|
+
let t = e.columns.map((e) => ` ${Zt(e)}`).join(",\n"), n = [`CREATE TABLE IF NOT EXISTS ${W(e.table)} (\n${t}${J(e, W)}\n)`];
|
|
1792
|
+
for (let t of e.indexes) t.name !== `${e.table}_pkey` && n.push(`CREATE ${t.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${W(t.name)} ON ${W(e.table)} (${t.columns.map(yn).join(", ")})`);
|
|
1793
|
+
return n;
|
|
1794
|
+
}, $t = 191, en = (e) => {
|
|
1795
|
+
let t = U(e).toUpperCase();
|
|
1796
|
+
return t.endsWith("TEXT") || t.endsWith("BLOB");
|
|
1797
|
+
}, tn = (e) => {
|
|
1798
|
+
if (e.type === "id" && e.idScheme?.kind === "numeric") return A(e.name, "mariadb");
|
|
1799
|
+
let t = q(e, "mariadb");
|
|
1800
|
+
if (t) return `${G(e.name)} ${U(e)} ${t}`;
|
|
1801
|
+
let n = [G(e.name), U(e)];
|
|
1802
|
+
if (e.type === "id" && n.push("PRIMARY KEY"), e.nullable || n.push("NOT NULL"), e.unique && e.type !== "id" && !en(e) && n.push("UNIQUE"), e.hasDefault) {
|
|
1803
|
+
let t = Yt(e.defaultValue);
|
|
1804
|
+
t && n.push(t);
|
|
1805
|
+
}
|
|
1806
|
+
return e.references && (n.push(`REFERENCES ${G(e.references.table)} (${G(e.references.column)})`), e.references.onDelete && n.push(`ON DELETE ${{
|
|
1807
|
+
cascade: "CASCADE",
|
|
1808
|
+
restrict: "RESTRICT",
|
|
1809
|
+
setNull: "SET NULL",
|
|
1810
|
+
noAction: "NO ACTION"
|
|
1811
|
+
}[e.references.onDelete]}`)), n.join(" ");
|
|
1812
|
+
}, nn = (e) => {
|
|
1813
|
+
let t = new Map(e.columns.map((e) => [e.name, e])), n = (e) => {
|
|
1814
|
+
let n = t.get(e);
|
|
1815
|
+
return n && en(n) ? `${G(e)}(${$t})` : G(e);
|
|
1816
|
+
}, r = e.columns.map((e) => ` ${tn(e)}`).join(",\n"), i = [`CREATE TABLE IF NOT EXISTS ${G(e.table)} (\n${r}${J(e, n)}\n)`];
|
|
1817
|
+
for (let t of e.columns) t.unique && t.type !== "id" && en(t) && i.push(`CREATE UNIQUE INDEX IF NOT EXISTS ${G(`${e.table}_${t.name}_key`)} ON ${G(e.table)} (${n(t.name)})`);
|
|
1818
|
+
for (let t of e.indexes) t.name !== `${e.table}_pkey` && i.push(`CREATE ${t.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${G(t.name)} ON ${G(e.table)} (${t.columns.map(n).join(", ")})`);
|
|
1819
|
+
return i;
|
|
1820
|
+
}, rn = {
|
|
1821
|
+
cascade: "CASCADE",
|
|
1822
|
+
restrict: "RESTRICT",
|
|
1823
|
+
setNull: "SET NULL",
|
|
1824
|
+
noAction: "NO ACTION"
|
|
1825
|
+
}, an = {
|
|
1826
|
+
cascade: "CASCADE",
|
|
1827
|
+
restrict: "NO ACTION",
|
|
1828
|
+
setNull: "SET NULL",
|
|
1829
|
+
noAction: "NO ACTION"
|
|
1830
|
+
}, on = (e) => {
|
|
1831
|
+
switch (e.type) {
|
|
1832
|
+
case "integer":
|
|
1833
|
+
case "boolean": return "INTEGER";
|
|
1834
|
+
case "bigint": return "INTEGER";
|
|
1835
|
+
case "real": return "REAL";
|
|
1836
|
+
case "decimal": return "NUMERIC";
|
|
1837
|
+
case "bytes": return "BLOB";
|
|
1838
|
+
default: return "TEXT";
|
|
1839
|
+
}
|
|
1840
|
+
}, Y = (e, t) => e == null ? null : e === "now" ? `DEFAULT ${t}` : typeof e == "boolean" ? `DEFAULT ${+!!e}` : typeof e == "number" ? `DEFAULT ${e}` : typeof e == "string" ? /[()]|^'/.test(e) ? `DEFAULT ${e}` : `DEFAULT '${e.replace(/'/g, "''")}'` : typeof e == "object" && !Array.isArray(e) ? k(e, "sqlite") : null, sn = (e) => {
|
|
1841
|
+
if (e.type === "id" && e.idScheme?.kind === "numeric") return A(e.name, "sqlite");
|
|
1842
|
+
let t = q(e, "sqlite");
|
|
1843
|
+
if (t) return `${W(e.name)} ${on(e)} ${t}`;
|
|
1844
|
+
let n = [W(e.name), on(e)];
|
|
1845
|
+
if (e.type === "id" && n.push("PRIMARY KEY"), e.nullable || n.push("NOT NULL"), e.unique && e.type !== "id" && n.push("UNIQUE"), e.hasDefault) {
|
|
1846
|
+
let t = Y(e.defaultValue, "CURRENT_TIMESTAMP");
|
|
1847
|
+
t && n.push(t);
|
|
1848
|
+
}
|
|
1849
|
+
if (e.references && (n.push(`REFERENCES ${W(e.references.table)} (${W(e.references.column)})`), e.references.onDelete && n.push(`ON DELETE ${rn[e.references.onDelete]}`)), e.oneOf && e.oneOf.length > 0) {
|
|
1850
|
+
let t = e.oneOf.map((e) => `'${e.replace(/'/g, "''")}'`).join(", ");
|
|
1851
|
+
n.push(`CHECK (${W(e.name)} IN (${t}))`);
|
|
1852
|
+
}
|
|
1853
|
+
return n.join(" ");
|
|
1854
|
+
}, cn = (e) => {
|
|
1855
|
+
let t = e.columns.map((e) => ` ${sn(e)}`).join(",\n"), n = [`CREATE TABLE IF NOT EXISTS ${W(e.table)} (\n${t}${J(e, W)}\n)`];
|
|
1856
|
+
for (let t of e.indexes) t.name !== `${e.table}_pkey` && n.push(`CREATE ${t.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${W(t.name)} ON ${W(e.table)} (${t.columns.map(W).join(", ")})`);
|
|
1857
|
+
return n;
|
|
1858
|
+
}, X = (e, t) => {
|
|
1859
|
+
let n = t.find((t) => t.tableName === e);
|
|
1860
|
+
if (!n) return [];
|
|
1861
|
+
let r = F([n]).tables[0], i = `${e}__voltro_rebuild`, a = r.columns.map((e) => W(e.name)).join(", "), o = [
|
|
1862
|
+
...cn({
|
|
1863
|
+
kind: "create-table",
|
|
1864
|
+
table: i,
|
|
1865
|
+
columns: r.columns,
|
|
1866
|
+
indexes: [],
|
|
1867
|
+
...r.primaryKey ? { primaryKey: r.primaryKey } : {}
|
|
1868
|
+
}),
|
|
1869
|
+
`INSERT INTO ${W(i)} (${a}) SELECT ${a} FROM ${W(e)}`,
|
|
1870
|
+
`DROP TABLE ${W(e)}`,
|
|
1871
|
+
`ALTER TABLE ${W(i)} RENAME TO ${W(e)}`
|
|
1872
|
+
];
|
|
1873
|
+
for (let t of r.indexes) t.name !== `${e}_pkey` && o.push(`CREATE ${t.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${W(t.name)} ON ${W(e)} (${t.columns.map(W).join(", ")})`);
|
|
1874
|
+
return o;
|
|
1875
|
+
}, ln = (e, t, n, r) => {
|
|
1876
|
+
let i = r ?? e.nullable;
|
|
1877
|
+
return `ALTER TABLE ${Z(n)} ALTER COLUMN ${Z(t)} ${O(e, "mssql")} ${i ? "NULL" : "NOT NULL"};`;
|
|
1878
|
+
}, un = (e) => {
|
|
1879
|
+
let t = `DECLARE @dc sysname; SELECT @dc = dc.name FROM sys.default_constraints dc JOIN sys.columns col ON col.object_id = dc.parent_object_id AND col.column_id = dc.parent_column_id WHERE dc.parent_object_id = OBJECT_ID(N'${e.table.replace(/'/g, "''")}') AND col.name = N'${e.column.replace(/'/g, "''")}'; IF @dc IS NOT NULL EXEC(N'ALTER TABLE ${Z(e.table)} DROP CONSTRAINT [' + @dc + ']');`;
|
|
1880
|
+
if (!e.hasDefault) return t;
|
|
1881
|
+
let n = Y(e.defaultValue, "SYSUTCDATETIME()");
|
|
1882
|
+
if (!n) return t;
|
|
1883
|
+
let r = n.replace(/^DEFAULT\s+/i, "");
|
|
1884
|
+
return `${t} ALTER TABLE ${Z(e.table)} ADD DEFAULT ${r} FOR ${Z(e.column)};`;
|
|
1885
|
+
}, Z = (e) => `[${e.replace(/]/g, "]]")}]`, dn = (e) => {
|
|
1886
|
+
switch (e.type) {
|
|
1887
|
+
case "id":
|
|
1888
|
+
case "reference": return "NVARCHAR(64)";
|
|
1889
|
+
case "integer": return "INT";
|
|
1890
|
+
case "real": return "FLOAT";
|
|
1891
|
+
case "decimal": return Ht(e, "DECIMAL");
|
|
1892
|
+
case "bigint": return "BIGINT";
|
|
1893
|
+
case "boolean": return "BIT";
|
|
1894
|
+
case "timestamp": return "DATETIME2(6)";
|
|
1895
|
+
case "date": return "DATE";
|
|
1896
|
+
case "bytes": return "VARBINARY(MAX)";
|
|
1897
|
+
default: return "NVARCHAR(MAX)";
|
|
1898
|
+
}
|
|
1899
|
+
}, fn = (e, t) => {
|
|
1900
|
+
if (e.type === "id" && e.idScheme?.kind === "numeric") return A(e.name, "mssql");
|
|
1901
|
+
let n = e.type === "text" && t?.has(e.name) ? "NVARCHAR(450)" : dn(e), r = q(e, "mssql");
|
|
1902
|
+
if (r) return `${Z(e.name)} ${r}`;
|
|
1903
|
+
let i = [Z(e.name), n];
|
|
1904
|
+
if (e.type === "id" && i.push("PRIMARY KEY"), e.nullable || i.push("NOT NULL"), e.unique && e.type !== "id" && i.push("UNIQUE"), e.hasDefault) {
|
|
1905
|
+
let t = Y(e.defaultValue, "SYSUTCDATETIME()");
|
|
1906
|
+
t && i.push(t);
|
|
1907
|
+
}
|
|
1908
|
+
return e.references && (i.push(`REFERENCES ${Z(e.references.table)} (${Z(e.references.column)})`), e.references.onDelete && i.push(`ON DELETE ${an[e.references.onDelete]}`)), i.join(" ");
|
|
1909
|
+
}, pn = (e) => {
|
|
1910
|
+
let t = (e) => e.replace(/'/g, "''"), n = new Set(e.indexes.flatMap((e) => e.columns)), r = e.columns.map((e) => ` ${fn(e, n)}`).join(",\n"), i = `CREATE TABLE ${Z(e.table)} (\n${r}${J(e, Z)}\n)`, a = [`IF OBJECT_ID(N'${t(e.table)}', N'U') IS NULL EXEC(N'${t(i)}')`];
|
|
1911
|
+
for (let n of e.indexes) {
|
|
1912
|
+
if (n.name === `${e.table}_pkey`) continue;
|
|
1913
|
+
let r = `CREATE ${n.unique ? "UNIQUE " : ""}INDEX ${Z(n.name)} ON ${Z(e.table)} (${n.columns.map(Z).join(", ")})`;
|
|
1914
|
+
a.push(`IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'${t(n.name)}' AND object_id = OBJECT_ID(N'${t(e.table)}')) EXEC(N'${t(r)}')`);
|
|
1915
|
+
}
|
|
1916
|
+
return a;
|
|
1917
|
+
}, mn = (e) => `__dropped_${e.replace(/[-:T.Z]/g, "").slice(0, 14)}`, hn = (e, t) => `DROP TABLE IF EXISTS ${t(e.table)};`, gn = (e, t, n) => {
|
|
1918
|
+
let r = `${e.table}${mn(t)}`;
|
|
1919
|
+
return `ALTER TABLE ${n(e.table)} RENAME TO ${n(r)};`;
|
|
1920
|
+
}, _n = (e, t, n) => {
|
|
1921
|
+
let r = K(e, t, n);
|
|
1922
|
+
return r ? en({ type: r.type }) : !1;
|
|
1923
|
+
}, vn = (e, t, n) => _n(e, t, n) ? `${G(n)}(${$t})` : G(n), yn = (e) => e.startsWith("(") ? e : W(e), bn = (e, t) => `CREATE ${e.index.unique ? "UNIQUE " : ""}INDEX ${t ? "CONCURRENTLY " : ""}IF NOT EXISTS ${W(e.index.name)} ON ${W(e.table)} (${e.index.columns.map(yn).join(", ")});`, xn = (e, t) => `CREATE ${e.index.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${G(e.index.name)} ON ${G(e.table)} (${e.index.columns.map((n) => vn(t, e.table, n)).join(", ")});`, Sn = (e) => `DROP INDEX IF EXISTS ${W(e.index)};`, Cn = (e) => `DROP INDEX ${G(e.index)} ON ${G(e.table)};`, wn = (e) => `DROP INDEX IF EXISTS ${Z(e.index)} ON ${Z(e.table)};`, Tn = (e) => {
|
|
1924
|
+
let t = (e) => e.replace(/'/g, "''"), n = `CREATE ${e.index.unique ? "UNIQUE " : ""}INDEX ${Z(e.index.name)} ON ${Z(e.table)} (${e.index.columns.map(Z).join(", ")})`;
|
|
1925
|
+
return `IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'${t(e.index.name)}' AND object_id = OBJECT_ID(N'${t(e.table)}')) EXEC(N'${t(n)}');`;
|
|
1926
|
+
}, En = (e, t, n) => {
|
|
1927
|
+
let r = n.find((t) => t.tableName === e);
|
|
1928
|
+
if (!r) return [];
|
|
1929
|
+
let i = [];
|
|
1930
|
+
for (let n of t) {
|
|
1931
|
+
let t = r.fields[n];
|
|
1932
|
+
t && t.type === "text" && !t.generatedAs && i.push(`ALTER TABLE ${Z(e)} ALTER COLUMN ${Z(n)} NVARCHAR(450) ${t.nullable ? "NULL" : "NOT NULL"};`);
|
|
1933
|
+
}
|
|
1934
|
+
return i;
|
|
1935
|
+
}, Dn = (e) => `ALTER TABLE ${W(e.table)} ADD CONSTRAINT ${W(`${e.table}_${e.column}_key`)} UNIQUE (${W(e.column)});`, On = (e, t) => `ALTER TABLE ${G(e.table)} ADD CONSTRAINT ${G(`${e.table}_${e.column}_key`)} UNIQUE (${vn(t, e.table, e.column)});`, kn = (e) => {
|
|
1936
|
+
let t = e.fields.map((e) => W(e)).join(", ");
|
|
1937
|
+
return `ALTER TABLE ${W(e.table)} ADD CONSTRAINT ${W(e.name)} UNIQUE (${t});`;
|
|
1938
|
+
}, An = (e, t) => {
|
|
1939
|
+
let n = e.fields.map((n) => vn(t, e.table, n)).join(", ");
|
|
1940
|
+
return `ALTER TABLE ${G(e.table)} ADD CONSTRAINT ${G(e.name)} UNIQUE (${n});`;
|
|
1941
|
+
}, jn = (e) => `ALTER TABLE ${W(e.table)} DROP CONSTRAINT IF EXISTS ${W(e.name)};`, Mn = (e) => `ALTER TABLE ${G(e.table)} DROP INDEX ${G(e.name)};`, Nn = (e) => `ALTER TABLE ${W(e.table)} DROP CONSTRAINT IF EXISTS ${W(`${e.table}_${e.column}_key`)};`, Pn = (e) => `ALTER TABLE ${G(e.table)} DROP INDEX ${G(`${e.table}_${e.column}_key`)};`, Fn = (e, t) => {
|
|
1942
|
+
let n = [], r = t(e.table), i = t(e.column), a = t(e.targetTable), o = t(e.targetColumn);
|
|
1943
|
+
if (e.orphanPolicy) {
|
|
1944
|
+
let t = `${i} IS NOT NULL AND ${i} NOT IN (SELECT ${o} FROM ${a})`;
|
|
1945
|
+
n.push(e.orphanPolicy === "delete" ? `DELETE FROM ${r} WHERE ${t};` : `UPDATE ${r} SET ${i} = NULL WHERE ${t};`);
|
|
1946
|
+
}
|
|
1947
|
+
let s = [
|
|
1948
|
+
`ALTER TABLE ${r}`,
|
|
1949
|
+
`ADD CONSTRAINT ${t(`${e.table}_${e.column}_fkey`)}`,
|
|
1950
|
+
`FOREIGN KEY (${i})`,
|
|
1951
|
+
`REFERENCES ${a} (${o})`
|
|
1952
|
+
];
|
|
1953
|
+
return e.onDelete && s.push(`ON DELETE ${{
|
|
1954
|
+
cascade: "CASCADE",
|
|
1955
|
+
restrict: "RESTRICT",
|
|
1956
|
+
setNull: "SET NULL",
|
|
1957
|
+
noAction: "NO ACTION"
|
|
1958
|
+
}[e.onDelete]}`), n.push(s.join(" ") + ";"), n;
|
|
1959
|
+
}, In = (e) => `ALTER TABLE ${W(e.table)} DROP CONSTRAINT IF EXISTS ${W(`${e.table}_${e.column}_fkey`)};`, Ln = (e) => `ALTER TABLE ${G(e.table)} DROP FOREIGN KEY ${G(`${e.table}_${e.column}_fkey`)};`, Rn = (e, t) => {
|
|
1960
|
+
let n = e.values.map((e) => `'${e.replace(/'/g, "''")}'`).join(", ");
|
|
1961
|
+
return `ALTER TABLE ${t(e.table)} ADD CONSTRAINT ${t(`${e.table}_${e.column}_check`)} CHECK (${t(e.column)} IN (${n}));`;
|
|
1962
|
+
}, zn = (e) => `ALTER TABLE ${W(e.table)} DROP CONSTRAINT IF EXISTS ${W(`${e.table}_${e.column}_check`)};`, Bn = (e) => `ALTER TABLE ${G(e.table)} DROP CHECK ${G(`${e.table}_${e.column}_check`)};`, Vn = (e, t, n, r) => {
|
|
1963
|
+
let i = e.column, a = r === "mariadb", o = a ? G : r === "mssql" ? Z : W, s = a ? U(i) : r === "mssql" ? dn(i) : r === "sqlite" ? on(i) : H(i), c = r === "mssql" ? "ADD" : "ADD COLUMN", l = (e) => a ? Yt(e) : r === "mssql" ? Y(e, "SYSUTCDATETIME()") : r === "sqlite" ? Y(e, "CURRENT_TIMESTAMP") : Xt(e), u = q(i, r);
|
|
1964
|
+
if (u) {
|
|
1965
|
+
let t = r === "mssql" ? `${o(i.name)} ${u}` : `${o(i.name)} ${s} ${u}`;
|
|
1966
|
+
return {
|
|
1967
|
+
pre: [`ALTER TABLE ${o(e.table)} ${c} ${t};`],
|
|
1968
|
+
post: []
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
if (t.classification === "safe") {
|
|
1972
|
+
let t = [`ALTER TABLE ${o(e.table)} ${c} ${o(i.name)} ${s}`];
|
|
1973
|
+
return i.unique && t.push("UNIQUE"), {
|
|
1974
|
+
pre: [`${t.join(" ")};`],
|
|
1975
|
+
post: []
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
if (t.classification === "needs-default") {
|
|
1979
|
+
let t = l(i.defaultValue) ?? "DEFAULT NULL";
|
|
1980
|
+
return {
|
|
1981
|
+
pre: [`ALTER TABLE ${o(e.table)} ${c} ${o(i.name)} ${s} NOT NULL ${t};`],
|
|
1982
|
+
post: []
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
let d = n(e.table, i.name), f = a ? `ALTER TABLE ${o(e.table)} MODIFY COLUMN ${o(i.name)} ${s} NOT NULL;` : r === "mssql" ? `ALTER TABLE ${o(e.table)} ALTER COLUMN ${o(i.name)} ${s} NOT NULL;` : r === "sqlite" ? null : `ALTER TABLE ${o(e.table)} ALTER COLUMN ${o(i.name)} SET NOT NULL;`, p = {
|
|
1986
|
+
pre: [`ALTER TABLE ${o(e.table)} ${c} ${o(i.name)} ${s};`],
|
|
1987
|
+
post: f ? [f] : []
|
|
1988
|
+
};
|
|
1989
|
+
return d?.sql && (p.backfillKind = "sql", p.backfillSql = d.sql), d?.js && (p.backfillKind = "js", p.backfillJs = d.js), d?.batchSize !== void 0 && (p.batchSize = d.batchSize), d?.sleepMs !== void 0 && (p.sleepMs = d.sleepMs), p;
|
|
1990
|
+
}, Hn = (e) => {
|
|
1991
|
+
let t = new Map(e.map((e) => [e.tableName, e]));
|
|
1992
|
+
return (e, n) => {
|
|
1993
|
+
let r = t.get(e);
|
|
1994
|
+
if (!r) return;
|
|
1995
|
+
let i = r.fields[n];
|
|
1996
|
+
if (!i || !i.backfill) return;
|
|
1997
|
+
let a = i.backfill, o = {
|
|
1998
|
+
...a.batchSize === void 0 ? {} : { batchSize: a.batchSize },
|
|
1999
|
+
...a.sleepMs === void 0 ? {} : { sleepMs: a.sleepMs }
|
|
2000
|
+
};
|
|
2001
|
+
if (a.kind === "sql" && a.sql) return {
|
|
2002
|
+
sql: a.sql,
|
|
2003
|
+
...o
|
|
2004
|
+
};
|
|
2005
|
+
if (a.kind === "js" && a.fn) return {
|
|
2006
|
+
js: a.fn,
|
|
2007
|
+
...o
|
|
2008
|
+
};
|
|
2009
|
+
};
|
|
2010
|
+
}, Q = (e, t, n) => e.unsafe(t).pipe(d.tapError((e) => d.sync(() => {
|
|
2011
|
+
process.stderr.write(_(`applier: statement failed (op=${n})`, t, e, 400));
|
|
2012
|
+
})), d.map(() => void 0)), $ = (e, t, n) => d.forEach(t, (t) => Q(e, t, n), { discard: !0 }), Un = (e, t, n) => d.gen(function* () {
|
|
2013
|
+
let r = process.hrtime.bigint(), i = Hn(n.declared), a = t.op;
|
|
2014
|
+
switch (a.kind) {
|
|
2015
|
+
case "create-table": {
|
|
2016
|
+
let t = yield* e.onDialectOrElse({
|
|
2017
|
+
mysql: () => d.succeed(nn(a)),
|
|
2018
|
+
mssql: () => d.succeed(pn(a)),
|
|
2019
|
+
sqlite: () => d.succeed(cn(a)),
|
|
2020
|
+
orElse: () => d.succeed(Qt(a))
|
|
2021
|
+
});
|
|
2022
|
+
for (let n of t) yield* Q(e, n, a.kind);
|
|
2023
|
+
break;
|
|
2024
|
+
}
|
|
2025
|
+
case "drop-table": {
|
|
2026
|
+
let t = process.env.VOLTRO_SOFT_DROP === "1", n = (/* @__PURE__ */ new Date()).toISOString(), r = (e) => t ? gn(a, n, e) : hn(a, e);
|
|
2027
|
+
yield* e.onDialectOrElse({
|
|
2028
|
+
mysql: () => Q(e, r(G), a.kind),
|
|
2029
|
+
orElse: () => Q(e, r(W), a.kind)
|
|
2030
|
+
});
|
|
2031
|
+
break;
|
|
2032
|
+
}
|
|
2033
|
+
case "drop-column": {
|
|
2034
|
+
let t = process.env.VOLTRO_SOFT_DROP === "1", n = (/* @__PURE__ */ new Date()).toISOString();
|
|
2035
|
+
yield* e.onDialectOrElse({
|
|
2036
|
+
mysql: () => Q(e, t ? Kt(a, n, G) : Wt(a), a.kind),
|
|
2037
|
+
orElse: () => Q(e, t ? Kt(a, n, W) : Ut(a, W), a.kind)
|
|
2038
|
+
});
|
|
2039
|
+
break;
|
|
2040
|
+
}
|
|
2041
|
+
case "rename-column":
|
|
2042
|
+
yield* e.onDialectOrElse({
|
|
2043
|
+
mysql: () => Q(e, Gt(a, G), a.kind),
|
|
2044
|
+
mssql: () => Q(e, `EXEC sp_rename N'${a.table.replace(/'/g, "''")}.${a.from.replace(/'/g, "''")}', N'${a.to.replace(/'/g, "''")}', 'COLUMN';`, a.kind),
|
|
2045
|
+
orElse: () => Q(e, Gt(a, W), a.kind)
|
|
2046
|
+
});
|
|
2047
|
+
break;
|
|
2048
|
+
case "add-index":
|
|
2049
|
+
yield* e.onDialectOrElse({
|
|
2050
|
+
mysql: () => Q(e, xn(a, n.declared), a.kind),
|
|
2051
|
+
mssql: () => $(e, [...En(a.table, a.index.columns, n.declared), Tn(a)], a.kind),
|
|
2052
|
+
orElse: () => Q(e, bn(a, t.classification === "online-required"), a.kind)
|
|
2053
|
+
});
|
|
2054
|
+
break;
|
|
2055
|
+
case "drop-index":
|
|
2056
|
+
yield* e.onDialectOrElse({
|
|
2057
|
+
mysql: () => Q(e, Cn(a), a.kind),
|
|
2058
|
+
mssql: () => Q(e, wn(a), a.kind),
|
|
2059
|
+
orElse: () => Q(e, Sn(a), a.kind)
|
|
2060
|
+
});
|
|
2061
|
+
break;
|
|
2062
|
+
case "add-unique":
|
|
2063
|
+
yield* e.onDialectOrElse({
|
|
2064
|
+
mysql: () => Q(e, On(a, n.declared), a.kind),
|
|
2065
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2066
|
+
mssql: () => $(e, [...En(a.table, [a.column], n.declared), Dn(a)], a.kind),
|
|
2067
|
+
orElse: () => Q(e, Dn(a), a.kind)
|
|
2068
|
+
});
|
|
2069
|
+
break;
|
|
2070
|
+
case "drop-unique":
|
|
2071
|
+
yield* e.onDialectOrElse({
|
|
2072
|
+
mysql: () => Q(e, Pn(a), a.kind),
|
|
2073
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2074
|
+
orElse: () => Q(e, Nn(a), a.kind)
|
|
2075
|
+
});
|
|
2076
|
+
break;
|
|
2077
|
+
case "add-unique-composite":
|
|
2078
|
+
yield* e.onDialectOrElse({
|
|
2079
|
+
mysql: () => Q(e, An(a, n.declared), a.kind),
|
|
2080
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2081
|
+
mssql: () => $(e, [...En(a.table, a.fields, n.declared), kn(a)], a.kind),
|
|
2082
|
+
orElse: () => Q(e, kn(a), a.kind)
|
|
2083
|
+
});
|
|
2084
|
+
break;
|
|
2085
|
+
case "drop-unique-composite":
|
|
2086
|
+
yield* e.onDialectOrElse({
|
|
2087
|
+
mysql: () => Q(e, Mn(a), a.kind),
|
|
2088
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2089
|
+
orElse: () => Q(e, jn(a), a.kind)
|
|
2090
|
+
});
|
|
2091
|
+
break;
|
|
2092
|
+
case "add-foreign-key":
|
|
2093
|
+
yield* e.onDialectOrElse({
|
|
2094
|
+
mysql: () => $(e, Fn(a, G), a.kind),
|
|
2095
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2096
|
+
orElse: () => $(e, Fn(a, W), a.kind)
|
|
2097
|
+
});
|
|
2098
|
+
break;
|
|
2099
|
+
case "add-column": {
|
|
2100
|
+
let n = Vn(a, t, i, yield* e.onDialectOrElse({
|
|
2101
|
+
mysql: () => d.succeed("mariadb"),
|
|
2102
|
+
mssql: () => d.succeed("mssql"),
|
|
2103
|
+
sqlite: () => d.succeed("sqlite"),
|
|
2104
|
+
orElse: () => d.succeed("postgres")
|
|
2105
|
+
}));
|
|
2106
|
+
for (let t of n.pre) yield* Q(e, t, a.kind);
|
|
2107
|
+
if (n.backfillKind === "sql" && n.backfillSql) if (!(t.classification === "online-required" || n.batchSize !== void 0)) yield* e`UPDATE ${e(a.table)} SET ${e(a.column.name)} = ${n.backfillSql}`;
|
|
2108
|
+
else {
|
|
2109
|
+
let t = n.batchSize ?? 5e3, r = n.sleepMs ?? 0, i, o = 0;
|
|
2110
|
+
for (;;) {
|
|
2111
|
+
let s = i === void 0 ? yield* e`SELECT ${e("id")} FROM ${e(a.table)} WHERE ${e(a.column.name)} IS NULL ORDER BY ${e("id")} LIMIT ${t}` : yield* e`SELECT ${e("id")} FROM ${e(a.table)} WHERE ${e(a.column.name)} IS NULL AND ${e("id")} > ${i} ORDER BY ${e("id")} LIMIT ${t}`;
|
|
2112
|
+
if (s.length === 0) break;
|
|
2113
|
+
let c = s.map((e) => e.id);
|
|
2114
|
+
yield* e`UPDATE ${e(a.table)} SET ${e(a.column.name)} = ${n.backfillSql} WHERE ${e("id")} IN ${e.in(c)}`, o += s.length, yield* d.logInfo(`backfill ${a.table}.${a.column.name}: ${o} rows`).pipe(d.annotateLogs({
|
|
2115
|
+
scope: "voltro:migrate",
|
|
2116
|
+
mode: "sql-batched"
|
|
2117
|
+
})), i = s[s.length - 1].id, r > 0 && (yield* d.sleep(`${r} millis`));
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
else if (n.backfillKind === "js" && n.backfillJs) {
|
|
2121
|
+
let t = n.batchSize ?? 1e3, r = n.sleepMs ?? 0, i, o = 0;
|
|
2122
|
+
for (;;) {
|
|
2123
|
+
let s = i === void 0 ? yield* e`SELECT ${e("id")} FROM ${e(a.table)} WHERE ${e(a.column.name)} IS NULL ORDER BY ${e("id")} LIMIT ${t}` : yield* e`SELECT ${e("id")} FROM ${e(a.table)} WHERE ${e(a.column.name)} IS NULL AND ${e("id")} > ${i} ORDER BY ${e("id")} LIMIT ${t}`;
|
|
2124
|
+
if (s.length === 0) break;
|
|
2125
|
+
for (let t of s) {
|
|
2126
|
+
let r = yield* d.tryPromise({
|
|
2127
|
+
try: () => Promise.resolve(n.backfillJs({ id: t.id })),
|
|
2128
|
+
catch: (e) => e
|
|
2129
|
+
}).pipe(d.orDie);
|
|
2130
|
+
yield* e`UPDATE ${e(a.table)} SET ${e(a.column.name)} = ${r} WHERE ${e("id")} = ${t.id}`;
|
|
2131
|
+
}
|
|
2132
|
+
o += s.length, yield* d.logInfo(`backfill ${a.table}.${a.column.name}: ${o} rows processed`).pipe(d.annotateLogs({
|
|
2133
|
+
scope: "voltro:migrate",
|
|
2134
|
+
table: a.table,
|
|
2135
|
+
column: a.column.name
|
|
2136
|
+
})), i = s[s.length - 1].id, r > 0 && (yield* d.sleep(`${r} millis`));
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
for (let t of n.post) yield* Q(e, t, a.kind);
|
|
2140
|
+
break;
|
|
2141
|
+
}
|
|
2142
|
+
case "alter-column-nullability":
|
|
2143
|
+
yield* e.onDialectOrElse({
|
|
2144
|
+
mysql: () => {
|
|
2145
|
+
let t = K(n.declared, a.table, a.column);
|
|
2146
|
+
return t ? Q(e, `ALTER TABLE ${G(a.table)} MODIFY COLUMN ${Jt(t, a.column, a.toNullable)};`, a.kind) : d.die(/* @__PURE__ */ Error(`alter-column-nullability on mariadb needs the declared column '${a.table}.${a.column}' (to render MODIFY COLUMN); it was not in ctx.declared.`));
|
|
2147
|
+
},
|
|
2148
|
+
mssql: () => {
|
|
2149
|
+
let t = K(n.declared, a.table, a.column);
|
|
2150
|
+
return t ? Q(e, ln(t, a.column, a.table, a.toNullable), a.kind) : d.die(/* @__PURE__ */ Error(`alter-column-nullability on mssql needs the declared column '${a.table}.${a.column}'.`));
|
|
2151
|
+
},
|
|
2152
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2153
|
+
orElse: () => Q(e, `ALTER TABLE ${W(a.table)} ALTER COLUMN ${W(a.column)} ${a.toNullable ? "DROP NOT NULL" : "SET NOT NULL"};`, a.kind)
|
|
2154
|
+
});
|
|
2155
|
+
break;
|
|
2156
|
+
case "alter-column-default":
|
|
2157
|
+
yield* e.onDialectOrElse({
|
|
2158
|
+
mysql: () => Q(e, qt(a, G, Yt), a.kind),
|
|
2159
|
+
mssql: () => Q(e, un(a), a.kind),
|
|
2160
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2161
|
+
orElse: () => Q(e, qt(a, W, Xt), a.kind)
|
|
2162
|
+
});
|
|
2163
|
+
break;
|
|
2164
|
+
case "alter-column-type": {
|
|
2165
|
+
let r = H({ type: a.to }), i = a.using ? ` USING ${a.using}` : "";
|
|
2166
|
+
if (t.classification !== "online-required") {
|
|
2167
|
+
yield* e.onDialectOrElse({
|
|
2168
|
+
mysql: () => {
|
|
2169
|
+
let t = K(n.declared, a.table, a.column);
|
|
2170
|
+
return t ? Q(e, `ALTER TABLE ${G(a.table)} MODIFY COLUMN ${Jt(t, a.column)};`, a.kind) : d.die(/* @__PURE__ */ Error(`alter-column-type on mariadb needs the declared column '${a.table}.${a.column}' (to render MODIFY COLUMN); it was not in ctx.declared.`));
|
|
2171
|
+
},
|
|
2172
|
+
mssql: () => {
|
|
2173
|
+
let t = K(n.declared, a.table, a.column);
|
|
2174
|
+
return t ? Q(e, ln(t, a.column, a.table), a.kind) : d.die(/* @__PURE__ */ Error(`alter-column-type on mssql needs the declared column '${a.table}.${a.column}'.`));
|
|
2175
|
+
},
|
|
2176
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2177
|
+
orElse: () => Q(e, `ALTER TABLE ${W(a.table)} ALTER COLUMN ${W(a.column)} TYPE ${r}${i};`, a.kind)
|
|
2178
|
+
});
|
|
2179
|
+
break;
|
|
2180
|
+
}
|
|
2181
|
+
yield* e.onDialectOrElse({
|
|
2182
|
+
mysql: () => d.die(/* @__PURE__ */ Error(`online-required alter-column-type on ${a.table}.${a.column} is not supported on mariadb (the shadow-swap path is postgres-only). Use a smaller-table window or an external online-DDL tool.`)),
|
|
2183
|
+
orElse: () => d.void
|
|
2184
|
+
});
|
|
2185
|
+
let o = `${a.column}__shadow`, s = `${a.column}__old`;
|
|
2186
|
+
yield* e.unsafe(`ALTER TABLE ${W(a.table)} ADD COLUMN ${W(o)} ${r}`);
|
|
2187
|
+
let c = a.using ?? W(a.column), l;
|
|
2188
|
+
for (;;) {
|
|
2189
|
+
let t = l === void 0 ? yield* e`SELECT ${e("id")} FROM ${e(a.table)} WHERE ${e(o)} IS NULL ORDER BY ${e("id")} LIMIT 5000` : yield* e`SELECT ${e("id")} FROM ${e(a.table)} WHERE ${e(o)} IS NULL AND ${e("id")} > ${l} ORDER BY ${e("id")} LIMIT 5000`;
|
|
2190
|
+
if (t.length === 0) break;
|
|
2191
|
+
yield* e.unsafe(`UPDATE ${W(a.table)} SET ${W(o)} = ${c} WHERE ${W(a.column)} IS NOT NULL AND ${W(o)} IS NULL AND ${W("id")} IN (${t.map((e) => `'${e.id.replace(/'/g, "''")}'`).join(",")})`), l = t[t.length - 1].id;
|
|
2192
|
+
}
|
|
2193
|
+
yield* d.logInfo(`alter-column-type shadow ${a.table}.${a.column}: shadow populated`).pipe(d.annotateLogs({ scope: "voltro:migrate" })), yield* e.unsafe(`ALTER TABLE ${W(a.table)} RENAME COLUMN ${W(a.column)} TO ${W(s)}`), yield* e.unsafe(`ALTER TABLE ${W(a.table)} RENAME COLUMN ${W(o)} TO ${W(a.column)}`), yield* e.unsafe(`ALTER TABLE ${W(a.table)} DROP COLUMN ${W(s)}`);
|
|
2194
|
+
break;
|
|
2195
|
+
}
|
|
2196
|
+
case "drop-foreign-key":
|
|
2197
|
+
yield* e.onDialectOrElse({
|
|
2198
|
+
mysql: () => Q(e, Ln(a), a.kind),
|
|
2199
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2200
|
+
orElse: () => Q(e, In(a), a.kind)
|
|
2201
|
+
});
|
|
2202
|
+
break;
|
|
2203
|
+
case "add-check":
|
|
2204
|
+
yield* e.onDialectOrElse({
|
|
2205
|
+
mysql: () => Q(e, Rn(a, G), a.kind),
|
|
2206
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2207
|
+
orElse: () => Q(e, Rn(a, W), a.kind)
|
|
2208
|
+
});
|
|
2209
|
+
break;
|
|
2210
|
+
case "drop-check":
|
|
2211
|
+
yield* e.onDialectOrElse({
|
|
2212
|
+
mysql: () => Q(e, Bn(a), a.kind),
|
|
2213
|
+
sqlite: () => $(e, X(a.table, n.declared), a.kind),
|
|
2214
|
+
orElse: () => Q(e, zn(a), a.kind)
|
|
2215
|
+
});
|
|
2216
|
+
break;
|
|
2217
|
+
}
|
|
2218
|
+
return {
|
|
2219
|
+
op: a,
|
|
2220
|
+
status: "applied",
|
|
2221
|
+
durationMs: Number((process.hrtime.bigint() - r) / 1000000n)
|
|
2222
|
+
};
|
|
2223
|
+
}), Wn = (e, t, n) => d.gen(function* () {
|
|
2224
|
+
let r = t.operations.filter((e) => e.blocked);
|
|
2225
|
+
if (r.length > 0) return yield* d.die(/* @__PURE__ */ Error(`applyPlan: refusing to execute — ${r.length} blocked operations. Run \`voltro db plan\` to see the fixes.`));
|
|
2226
|
+
yield* w(e);
|
|
2227
|
+
let i = Date.now(), a = [];
|
|
2228
|
+
try {
|
|
2229
|
+
let r = yield* e.onDialectOrElse({
|
|
2230
|
+
pg: () => d.succeed(!0),
|
|
2231
|
+
mssql: () => d.succeed(!0),
|
|
2232
|
+
orElse: () => d.succeed(!1)
|
|
2233
|
+
}), i = r ? t.operations.filter((e) => e.classification !== "online-required") : [], o = r ? t.operations.filter((e) => e.classification === "online-required") : t.operations;
|
|
2234
|
+
i.length > 0 && (yield* e.withTransaction(d.gen(function* () {
|
|
2235
|
+
let e = yield* p.SqlClient;
|
|
2236
|
+
for (let t of i) a.push(yield* Un(e, t, n));
|
|
2237
|
+
})));
|
|
2238
|
+
for (let t of o) a.push(yield* Un(e, t, n));
|
|
2239
|
+
} finally {
|
|
2240
|
+
yield* T(e).pipe(d.orDie);
|
|
2241
|
+
}
|
|
2242
|
+
let o = Date.now() - i, s = `plan_${Date.now().toString(36)}`, c = (/* @__PURE__ */ new Date()).toISOString(), l = yield* e.onDialectOrElse({
|
|
2243
|
+
mysql: () => d.succeed(c.replace("T", " ").replace("Z", "")),
|
|
2244
|
+
orElse: () => d.succeed(c)
|
|
2245
|
+
});
|
|
2246
|
+
return yield* e`
|
|
2247
|
+
INSERT INTO ${e("_voltro_migration_plans")}
|
|
2248
|
+
(${e("id")}, ${e("fingerprint")}, ${e("operations")},
|
|
2249
|
+
${e("appliedBy")}, ${e("environment")}, ${e("source")},
|
|
2250
|
+
${e("durationMs")}, ${e("appliedAt")}, ${e("notes")})
|
|
2251
|
+
VALUES
|
|
2252
|
+
(${s}, ${t.toFingerprint}, ${JSON.stringify(t.operations)},
|
|
2253
|
+
${n.appliedBy}, ${n.environment}, ${n.source},
|
|
2254
|
+
${o}, ${l}, ${n.notes ?? null})
|
|
2255
|
+
`, {
|
|
2256
|
+
id: s,
|
|
2257
|
+
appliedAt: c,
|
|
2258
|
+
fingerprint: t.toFingerprint,
|
|
2259
|
+
operations: t.operations,
|
|
2260
|
+
appliedBy: n.appliedBy,
|
|
2261
|
+
environment: n.environment,
|
|
2262
|
+
durationMs: o,
|
|
2263
|
+
source: n.source,
|
|
2264
|
+
...n.notes ? { notes: n.notes } : {}
|
|
2265
|
+
};
|
|
2266
|
+
}), Gn = (e) => e.operations.filter((e) => e.blocked).map((e) => ` - ${e.op.kind} ${"table" in e.op ? `[${e.op.table}]` : ""}: ${e.reason ?? "<no reason>"}\n fix: ${e.blocked?.fix ?? "<no fix>"}`).join("\n"), Kn = (e) => e`
|
|
2267
|
+
SELECT fingerprint
|
|
2268
|
+
FROM _voltro_migration_plans
|
|
2269
|
+
ORDER BY ${e("appliedAt")} DESC
|
|
2270
|
+
LIMIT 1
|
|
2271
|
+
`.pipe(d.map((e) => e.length > 0 ? e[0].fingerprint : void 0), d.catchAll(() => d.succeed(void 0))), qn = (e, t) => d.gen(function* () {
|
|
2272
|
+
let n = N(F(t)), r = yield* Kn(e);
|
|
2273
|
+
return r === n ? {
|
|
2274
|
+
kind: "up-to-date",
|
|
2275
|
+
fingerprint: n
|
|
2276
|
+
} : {
|
|
2277
|
+
kind: "prod-mismatch",
|
|
2278
|
+
expected: n,
|
|
2279
|
+
...r === void 0 ? {} : { actual: r }
|
|
2280
|
+
};
|
|
2281
|
+
}), Jn = [
|
|
2282
|
+
"pgeagertest_",
|
|
2283
|
+
"mig_e2e_",
|
|
2284
|
+
"mig_test_",
|
|
2285
|
+
"mysqleagertest_",
|
|
2286
|
+
"mssqleagertest_",
|
|
2287
|
+
"sqliteeagertest_"
|
|
2288
|
+
], Yn = (e) => Jn.some((t) => e.startsWith(t)), Xn = (e, t) => d.gen(function* () {
|
|
2289
|
+
if (process.env.VOLTRO_TEST_FIXTURE_GC === "off") return [];
|
|
2290
|
+
let n = t.filter((e) => Yn(e.name)).map((e) => e.name);
|
|
2291
|
+
if (n.length === 0) return [];
|
|
2292
|
+
for (let t of n) yield* e.unsafe(`DROP TABLE IF EXISTS "${t}" CASCADE`).pipe(d.catchAll(() => d.void));
|
|
2293
|
+
return n;
|
|
2294
|
+
}), Zn = () => {
|
|
2295
|
+
let e = (process.env.VOLTRO_DB_IGNORE_TABLES ?? "").split(",").map((e) => e.trim()).filter((e) => e.length > 0);
|
|
2296
|
+
return e.length > 0 ? { ignoreTables: e } : {};
|
|
2297
|
+
}, Qn = (e, t, n) => d.gen(function* () {
|
|
2298
|
+
let r = t.filter((e) => !$e(e.tableName));
|
|
2299
|
+
if (process.env.VOLTRO_MIGRATE_FORCE !== "1") {
|
|
2300
|
+
let t = N(F(r));
|
|
2301
|
+
if ((yield* Kn(e)) === t) return {
|
|
2302
|
+
kind: "up-to-date",
|
|
2303
|
+
fingerprint: t
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
let i = yield* Vt(e), a = yield* Xn(e, i.tables);
|
|
2307
|
+
a.length > 0 && (yield* d.logInfo(`auto-migrate: reaped ${a.length} test-fixture leftover(s)`).pipe(d.annotateLogs({
|
|
2308
|
+
scope: "voltro:migrate",
|
|
2309
|
+
count: a.length
|
|
2310
|
+
})), i = yield* Vt(e));
|
|
2311
|
+
let o = wt({
|
|
2312
|
+
declared: r,
|
|
2313
|
+
live: { tables: i.tables.filter((e) => !$e(e.name)) },
|
|
2314
|
+
...Zn()
|
|
2315
|
+
});
|
|
2316
|
+
if (o.summary.blocked > 0) {
|
|
2317
|
+
let e = o.operations.filter((e) => e.blocked).every((e) => e.classification === "lossy");
|
|
2318
|
+
if (process.env.VOLTRO_DESTRUCTIVE_OK !== "1" || !e) return {
|
|
2319
|
+
kind: "refused-blocked",
|
|
2320
|
+
plan: o
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
return o.operations.length === 0 ? {
|
|
2324
|
+
kind: "up-to-date",
|
|
2325
|
+
fingerprint: o.toFingerprint
|
|
2326
|
+
} : {
|
|
2327
|
+
kind: "applied",
|
|
2328
|
+
applied: yield* Wn(e, o, {
|
|
2329
|
+
declared: t,
|
|
2330
|
+
appliedBy: n.appliedBy,
|
|
2331
|
+
environment: n.environment === "prod" ? "dev" : n.environment,
|
|
2332
|
+
source: "auto-diff"
|
|
2333
|
+
}),
|
|
2334
|
+
plan: o
|
|
2335
|
+
};
|
|
2336
|
+
}), $n = (e, t, n) => process.env.VOLTRO_AUTO_MIGRATE === "0" ? d.succeed({
|
|
2337
|
+
kind: "skipped",
|
|
2338
|
+
reason: "VOLTRO_AUTO_MIGRATE=0"
|
|
2339
|
+
}) : n.environment === "prod" ? qn(e, t) : Qn(e, t, n), er = (e, t) => {
|
|
2340
|
+
switch (e.kind) {
|
|
2341
|
+
case "skipped": return `auto-migrate: skipped (${e.reason})`;
|
|
2342
|
+
case "up-to-date": return `auto-migrate: schema up to date (${t}, fingerprint=${P(e.fingerprint)})`;
|
|
2343
|
+
case "applied": {
|
|
2344
|
+
let t = e.plan.summary;
|
|
2345
|
+
return `auto-migrate: applied ${e.plan.operations.length} op(s) in ${e.applied.durationMs}ms [safe=${t.safe} needs-default=${t.needsDefault} needs-backfill=${t.needsBackfill} rename=${t.needsRenameAnnotation} lossy=${t.lossy}] fingerprint=${P(e.applied.fingerprint)}`;
|
|
2346
|
+
}
|
|
2347
|
+
case "prod-mismatch": return `auto-migrate: SCHEMA FINGERPRINT MISMATCH — declared=${P(e.expected)}` + (e.actual ? ` live=${P(e.actual)}` : " live=<no _voltro_migration_plans row>") + ". Run `voltro db apply --plan plan.json` from the deploy pipeline before serving.";
|
|
2348
|
+
case "refused-blocked": {
|
|
2349
|
+
let t = e.plan.operations.filter((e) => e.blocked), n = t.filter((e) => e.op.kind === "drop-table").length, r = t.filter((e) => e.op.kind === "add-column").length, i = t.filter((e) => e.op.kind === "drop-column").length, a = t.filter((e) => e.classification === "needs-rename-annotation").length, o = `auto-migrate: REFUSED — ${e.plan.summary.blocked} blocked operation(s):\n` + Gn(e.plan), s = [""];
|
|
2350
|
+
return n > 0 && s.push(`${n} table(s) exist in the live DB but aren't in your declared schema.`, "This is usually one of three things:", " (a) You're iterating on the schema — the table file got renamed, moved, or temporarily commented out.", " → Restore the declaration. Your data stays intact.", " (b) You want to KEEP the table but NOT manage it with Voltro (a leftover from another tool —", " a Strapi/Rails/legacy migration artifact, or an externally-owned table).", ` → Set \`VOLTRO_DB_IGNORE_TABLES=${t.filter((e) => e.op.kind === "drop-table").map((e) => "table" in e.op ? e.op.table : "").filter(Boolean).join(",")}\` (comma-separated).`, " The planner excludes those tables from the diff entirely — it never drops them, and they", " stop blocking your OTHER (additive) changes. This is the fix when a leftover freezes your schema.", " (c) You actually want to drop the table.", " → Add it to your declared set + chain `.dropped()` on it, OR", " write a file-based migration in `migrations/` that DROPs it, OR", " set `VOLTRO_DESTRUCTIVE_OK=1` for ONE `voltro dev` run (acknowledges data loss).", " Soft-drop: set `VOLTRO_SOFT_DROP=1` alongside DESTRUCTIVE_OK to RENAME instead of DROP", " (data survives as `<name>__dropped_<ts>` for ~7d; restorable via `voltro db restore-snapshot`)."), r > 0 && s.push(`${r} required column(s) need a backfill strategy before adding to a populated table.`, " → Annotate the column: `.backfill(sql`<expr>`)` or `.backfill(row => <fn>)` or `.default(<value>)`.", " Existing rows get the backfill value; the column then lands as NOT NULL."), i > 0 && s.push(`${i} column(s) are missing from your declared schema.`, " → If intentional: chain `.dropped()` on the column slot in the table declaration.", " → If a typo: restore the field. Data survives until you drop it."), a > 0 && s.push(`${a} column(s) look like renames (one column gone + a new one of same shape appeared).`, " → On the NEW column add `.renamedFrom('<oldName>')` so the planner emits RENAME instead of DROP+ADD."), s.push("", "See `voltro db plan` for the full machine-readable diff."), o + "\n" + s.join("\n");
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}, tr = /* @__PURE__ */ new Set([
|
|
2354
|
+
"node_modules",
|
|
2355
|
+
"dist",
|
|
2356
|
+
"build",
|
|
2357
|
+
".turbo",
|
|
2358
|
+
".vite",
|
|
2359
|
+
".next",
|
|
2360
|
+
".git",
|
|
2361
|
+
".framework"
|
|
2362
|
+
]), nr = async (e) => {
|
|
2363
|
+
let t = [], n = async (e) => {
|
|
2364
|
+
let r = await h.readdir(e, { withFileTypes: !0 }).catch(() => []);
|
|
2365
|
+
for (let i of r) {
|
|
2366
|
+
if (tr.has(i.name)) continue;
|
|
2367
|
+
let r = g(e, i.name);
|
|
2368
|
+
i.isDirectory() ? await n(r) : i.isFile() && u.test(i.name) && t.push(r);
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
return await n(e), t.sort((e, t) => {
|
|
2372
|
+
let n = e.split("/").pop() ?? e, r = t.split("/").pop() ?? t;
|
|
2373
|
+
return n.localeCompare(r);
|
|
2374
|
+
});
|
|
2375
|
+
}, rr = async (e) => {
|
|
2376
|
+
let t = [];
|
|
2377
|
+
for (let n of e) {
|
|
2378
|
+
let e = (await import(ee(n).href)).default;
|
|
2379
|
+
if (!s(e)) {
|
|
2380
|
+
process.stderr.write(`[voltro:migrate] ${n} has no default-exported \`migration(...)\` — skipped\n`);
|
|
2381
|
+
continue;
|
|
2382
|
+
}
|
|
2383
|
+
t.push({
|
|
2384
|
+
file: n,
|
|
2385
|
+
migration: e
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
return t;
|
|
2389
|
+
}, ir = (e) => e`
|
|
2390
|
+
SELECT ${e("id")}
|
|
2391
|
+
FROM ${e("_voltro_migration_plans")}
|
|
2392
|
+
WHERE ${e("source")} = 'file'
|
|
2393
|
+
`.pipe(d.map((e) => e.map((e) => e.id)), d.catchAll(() => d.succeed([]))), ar = (e, t, n, r) => d.gen(function* () {
|
|
2394
|
+
let i = Date.now(), a = (/* @__PURE__ */ new Date()).toISOString(), o = {
|
|
2395
|
+
sql: e,
|
|
2396
|
+
log: {
|
|
2397
|
+
info: (e, n) => d.logInfo(e).pipe(d.annotateLogs({
|
|
2398
|
+
scope: `migrations:file:${t.migration.id}`,
|
|
2399
|
+
...n ?? {}
|
|
2400
|
+
})),
|
|
2401
|
+
warn: (e, n) => d.logWarning(e).pipe(d.annotateLogs({
|
|
2402
|
+
scope: `migrations:file:${t.migration.id}`,
|
|
2403
|
+
...n ?? {}
|
|
2404
|
+
}))
|
|
2405
|
+
},
|
|
2406
|
+
appliedAt: a
|
|
2407
|
+
}, s = t.migration.up(o);
|
|
2408
|
+
d.isEffect(s) ? yield* s : yield* d.tryPromise({
|
|
2409
|
+
try: () => s,
|
|
2410
|
+
catch: (e) => e
|
|
2411
|
+
});
|
|
2412
|
+
let c = Date.now() - i;
|
|
2413
|
+
return !d.isEffect(s) && c < 2 && (yield* d.logWarning(`migration ${t.migration.id}: up() resolved in ${c}ms — did the body \`await\` an \`@effect/sql\` Effect by accident? \`sql.unsafe(...)\` returns an Effect, not a Promise; \`await\` on it is a silent no-op. Switch the body to \`Effect.gen(function* () { yield* sql.unsafe(...) })\` form.`).pipe(d.annotateLogs({ scope: `migrations:file:${t.migration.id}` }))), yield* e`
|
|
2414
|
+
INSERT INTO ${e("_voltro_migration_plans")}
|
|
2415
|
+
(${e("id")}, ${e("fingerprint")}, ${e("operations")},
|
|
2416
|
+
${e("appliedBy")}, ${e("environment")}, ${e("source")},
|
|
2417
|
+
${e("durationMs")}, ${e("appliedAt")}, ${e("notes")})
|
|
2418
|
+
VALUES
|
|
2419
|
+
(${t.migration.id}, ${""}, ${JSON.stringify([{
|
|
2420
|
+
kind: "file",
|
|
2421
|
+
file: t.file
|
|
2422
|
+
}])},
|
|
2423
|
+
${r}, ${n}, ${"file"},
|
|
2424
|
+
${c}, ${a}, ${t.migration.description})
|
|
2425
|
+
`, { durationMs: c };
|
|
2426
|
+
}), or = (e, t) => d.gen(function* () {
|
|
2427
|
+
let n = yield* d.tryPromise({
|
|
2428
|
+
try: () => nr(g(t.projectRoot, "migrations")),
|
|
2429
|
+
catch: (e) => e
|
|
2430
|
+
});
|
|
2431
|
+
if (n.length === 0) return {
|
|
2432
|
+
applied: [],
|
|
2433
|
+
skipped: []
|
|
2434
|
+
};
|
|
2435
|
+
let r = yield* d.tryPromise({
|
|
2436
|
+
try: () => rr(n),
|
|
2437
|
+
catch: (e) => e
|
|
2438
|
+
});
|
|
2439
|
+
if (r.length === 0) return {
|
|
2440
|
+
applied: [],
|
|
2441
|
+
skipped: []
|
|
2442
|
+
};
|
|
2443
|
+
let i = new Set(yield* ir(e)), a = r.filter((e) => !i.has(e.migration.id)), o = r.filter((e) => i.has(e.migration.id)).map((e) => e.migration.id);
|
|
2444
|
+
if (a.length === 0) return {
|
|
2445
|
+
applied: [],
|
|
2446
|
+
skipped: o
|
|
2447
|
+
};
|
|
2448
|
+
yield* w(e);
|
|
2449
|
+
let s = [];
|
|
2450
|
+
try {
|
|
2451
|
+
for (let n of a) {
|
|
2452
|
+
yield* d.logInfo("file-based migration applying").pipe(d.annotateLogs({
|
|
2453
|
+
scope: "voltro:migrate",
|
|
2454
|
+
id: n.migration.id,
|
|
2455
|
+
file: n.file
|
|
2456
|
+
}));
|
|
2457
|
+
let { durationMs: r } = yield* ar(e, n, t.env, t.appliedBy);
|
|
2458
|
+
s.push({
|
|
2459
|
+
id: n.migration.id,
|
|
2460
|
+
durationMs: r
|
|
2461
|
+
}), yield* d.logInfo("file-based migration applied").pipe(d.annotateLogs({
|
|
2462
|
+
scope: "voltro:migrate",
|
|
2463
|
+
id: n.migration.id,
|
|
2464
|
+
durationMs: r
|
|
2465
|
+
}));
|
|
2466
|
+
}
|
|
2467
|
+
} finally {
|
|
2468
|
+
yield* T(e).pipe(d.orDie);
|
|
2469
|
+
}
|
|
2470
|
+
return {
|
|
2471
|
+
applied: s,
|
|
2472
|
+
skipped: o
|
|
2473
|
+
};
|
|
2474
|
+
}), sr = (e, t) => e`
|
|
2475
|
+
SELECT ${e("id")}
|
|
2476
|
+
FROM ${e("_voltro_migration_plans")}
|
|
2477
|
+
WHERE ${e("id")} = ${t} AND ${e("source")} = 'file'
|
|
2478
|
+
LIMIT 1
|
|
2479
|
+
`, cr = (e, t) => d.gen(function* () {
|
|
2480
|
+
if (!(yield* sr(e, t.id))[0]) return yield* d.fail(/* @__PURE__ */ Error(`rollback: ${t.id} not found in _voltro_migration_plans (file source)`));
|
|
2481
|
+
let n = yield* d.tryPromise({
|
|
2482
|
+
try: () => nr(g(t.projectRoot, "migrations")),
|
|
2483
|
+
catch: (e) => e
|
|
2484
|
+
}), r = (yield* d.tryPromise({
|
|
2485
|
+
try: () => rr(n),
|
|
2486
|
+
catch: (e) => e
|
|
2487
|
+
})).find((e) => e.migration.id === t.id);
|
|
2488
|
+
if (!r) return yield* d.fail(/* @__PURE__ */ Error(`rollback: file for migration ${t.id} not found on disk under ${t.projectRoot}/migrations/`));
|
|
2489
|
+
yield* w(e);
|
|
2490
|
+
let i = Date.now();
|
|
2491
|
+
try {
|
|
2492
|
+
let n = {
|
|
2493
|
+
sql: e,
|
|
2494
|
+
log: {
|
|
2495
|
+
info: (e, n) => d.logInfo(e).pipe(d.annotateLogs({
|
|
2496
|
+
scope: `migrations:file:${t.id}`,
|
|
2497
|
+
...n ?? {}
|
|
2498
|
+
})),
|
|
2499
|
+
warn: (e, n) => d.logWarning(e).pipe(d.annotateLogs({
|
|
2500
|
+
scope: `migrations:file:${t.id}`,
|
|
2501
|
+
...n ?? {}
|
|
2502
|
+
}))
|
|
2503
|
+
},
|
|
2504
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2505
|
+
}, a = r.migration.down(n);
|
|
2506
|
+
d.isEffect(a) ? yield* a : yield* d.tryPromise({
|
|
2507
|
+
try: () => a,
|
|
2508
|
+
catch: (e) => e
|
|
2509
|
+
});
|
|
2510
|
+
let o = Date.now() - i;
|
|
2511
|
+
return yield* e`DELETE FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t.id} AND ${e("source")} = 'file'`, {
|
|
2512
|
+
id: t.id,
|
|
2513
|
+
durationMs: o
|
|
2514
|
+
};
|
|
2515
|
+
} finally {
|
|
2516
|
+
yield* T(e).pipe(d.orDie);
|
|
2517
|
+
}
|
|
2518
|
+
}), lr = (e) => d.gen(function* () {
|
|
2519
|
+
let t = yield* p.SqlClient, n = yield* t`
|
|
2520
|
+
SELECT id, fingerprint, appliedAt FROM _voltro_migration_plans
|
|
2521
|
+
WHERE source = 'auto-diff'
|
|
2522
|
+
AND squashedAt IS NULL
|
|
2523
|
+
AND appliedAt < ${e.before}
|
|
2524
|
+
ORDER BY appliedAt DESC
|
|
2525
|
+
`;
|
|
2526
|
+
if (n.length === 0) return yield* d.fail(/* @__PURE__ */ Error(`squash: no rows applied before ${e.before}`));
|
|
2527
|
+
let r = n[0];
|
|
2528
|
+
if (r.fingerprint !== e.fingerprint) return yield* d.fail(/* @__PURE__ */ Error(`squash: latest pre-squash fingerprint (${r.fingerprint.slice(0, 12)}…) differs from current declared-schema fingerprint (${e.fingerprint.slice(0, 12)}…). This means the working tree carries un-applied changes — squashing now would lock the drift in.\n\nFix: run \`voltro db apply\` first to align the live schema with the working tree, then re-squash.`));
|
|
2529
|
+
yield* t`
|
|
2530
|
+
UPDATE _voltro_migration_plans
|
|
2531
|
+
SET squashedAt = CURRENT_TIMESTAMP
|
|
2532
|
+
WHERE id IN ${t.in(n.map((e) => e.id))}
|
|
2533
|
+
`;
|
|
2534
|
+
let i = `plan_squash_${Math.floor(Date.now() / 1e3).toString(36)}`;
|
|
2535
|
+
return yield* t`
|
|
2536
|
+
INSERT INTO _voltro_migration_plans
|
|
2537
|
+
(id, fingerprint, operations, rollbackPlan, appliedBy, environment, source,
|
|
2538
|
+
onlineStrategy, durationMs, notes, squashedAt, appliedAt, createdAt, updatedAt)
|
|
2539
|
+
VALUES (
|
|
2540
|
+
${i},
|
|
2541
|
+
${e.fingerprint},
|
|
2542
|
+
${JSON.stringify({
|
|
2543
|
+
kind: "squash-snapshot",
|
|
2544
|
+
rowsSquashed: n.length
|
|
2545
|
+
})},
|
|
2546
|
+
${null},
|
|
2547
|
+
${e.appliedBy},
|
|
2548
|
+
${e.environment},
|
|
2549
|
+
${"squash-snapshot"},
|
|
2550
|
+
${null},
|
|
2551
|
+
${0},
|
|
2552
|
+
${e.note ?? null},
|
|
2553
|
+
${null},
|
|
2554
|
+
${"CURRENT_TIMESTAMP"},
|
|
2555
|
+
${"CURRENT_TIMESTAMP"},
|
|
2556
|
+
${"CURRENT_TIMESTAMP"}
|
|
2557
|
+
)
|
|
2558
|
+
`, {
|
|
2559
|
+
squashedCount: n.length,
|
|
2560
|
+
snapshotFingerprint: e.fingerprint,
|
|
2561
|
+
snapshotId: i
|
|
2562
|
+
};
|
|
2563
|
+
}), ur = (e, ...t) => ({
|
|
2564
|
+
_tag: "RawSqlFragment",
|
|
2565
|
+
strings: [...e],
|
|
2566
|
+
values: [...t]
|
|
2567
|
+
}), dr = (e) => typeof e == "object" && !!e && e._tag === "RawSqlFragment";
|
|
2568
|
+
//#endregion
|
|
2569
|
+
export { u as FILE_MIGRATION_PATTERN, b as VOLTRO_MIGRATION_LOCK_KEY, it as _internalCmp, w as acquireMigrationLock, Be as applyNamespacedSchema, Wn as applyPlan, Pe as applySchema, jt as chunkTables, F as declaredSnapshot, fe as defaultClause, k as defaultJsonClause, er as describeOutcome, Ut as emitDropColumnDdl, Wt as emitDropColumnDdlMysql, Ne as emitFrameworkBootstrapSql, je as emitNamespaceProvisionDdl, Me as emitNamespacedSchemaSql, M as emitSchemaSql, N as fingerprintSchema, Zn as ignoreTablesFromEnv, Vt as introspectSchema, s as isFileMigration, dr as isRawSqlFragment, Et as mapPgType, l as migration, A as numericIdSql, Tt as parseEnumCheck, wt as planMigrations, Ve as provisionTenantNamespace, T as releaseMigrationLock, tn as renderColumnMysql, Zt as renderColumnPg, cr as rollbackFileBasedMigration, or as runFileBasedMigrations, He as runFrameworkBootstrap, ze as runMigrate, $n as runPlannedMigrations, P as shortFingerprint, ur as sql, O as sqlType, lr as squashMigrationPlans, E as withMigrationLock };
|