@voltro/database 0.85.0 → 0.86.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 +666 -0
- package/THIRD-PARTY-NOTICES.md +5 -33
- package/dist/actorSnapshot.js +24 -0
- package/dist/arrayCodec.js +32 -0
- package/dist/branchExecute.js +92 -0
- package/dist/branching.js +106 -0
- package/dist/bulkInsertLimits.js +25 -0
- package/dist/cdcOffsetsTables.js +19 -0
- package/dist/changeEvent.js +41 -0
- package/dist/changeIdentity.js +65 -0
- package/dist/changeListenerCeiling.js +6 -0
- package/dist/columnReference.js +17 -0
- package/dist/columns.js +326 -0
- package/dist/committedChanges.js +12 -0
- package/dist/connectionIdentity.js +36 -0
- package/dist/coreTables.js +16 -0
- package/dist/coreTablesRegistry.js +10 -0
- package/dist/dataTransfers.js +22 -0
- package/dist/dbCause.js +222 -0
- package/dist/dbMetrics.js +65 -0
- package/dist/decimalComparison.js +27 -0
- package/dist/derivedRowId.js +5 -0
- package/dist/dialect.js +13 -0
- package/dist/enumRenames.js +14 -0
- package/dist/expiresMixin.js +10 -0
- package/dist/fieldEncryption.js +98 -0
- package/dist/frameworkTables.js +130 -0
- package/dist/ginIndex.js +10 -0
- package/dist/hybridSearch.js +32 -0
- package/dist/idGenerator.js +57 -0
- package/dist/identifierLimits.js +32 -0
- package/dist/index.d.ts +18 -7
- package/dist/index.js +81 -2183
- package/dist/indexAudit.js +61 -0
- package/dist/indexStorage.js +58 -0
- package/dist/joinCompiler.js +160 -0
- package/dist/jsonEagerCompiler.js +436 -0
- package/dist/jsonPatch.js +78 -0
- package/dist/jsonPatchAssignments.js +13 -0
- package/dist/jsonPath.js +31 -0
- package/dist/localFirstMixin.js +12 -0
- package/dist/migrate.js +624 -0
- package/dist/migration.js +18 -0
- package/dist/migrations/appliedAtValue.js +8 -0
- package/dist/migrations/applier.js +1085 -0
- package/dist/migrations/boot.js +196 -0
- package/dist/migrations/branchRehearsal.js +122 -0
- package/dist/migrations/columnEquivalence.js +85 -0
- package/dist/migrations/ddlError.js +63 -0
- package/dist/migrations/declaredSchema.js +162 -0
- package/dist/migrations/deployLedger.js +19 -0
- package/dist/migrations/fileBased.js +57 -0
- package/dist/migrations/fileContext.js +482 -0
- package/dist/migrations/fileDiscovery.js +318 -0
- package/dist/migrations/fileSkipProbes.js +117 -0
- package/dist/migrations/fingerprint.js +46 -0
- package/dist/migrations/fkCycles.js +13 -0
- package/dist/migrations/frameworkLiveTables.js +25 -0
- package/dist/migrations/idempotentDdl.js +37 -0
- package/dist/migrations/indexPredicate.js +50 -0
- package/dist/migrations/introspect.js +799 -0
- package/dist/migrations/ledgerRows.js +4 -0
- package/dist/migrations/lock.js +91 -0
- package/dist/migrations/migrationTableExists.js +9 -0
- package/dist/migrations/mssqlColumnDependencies.js +76 -0
- package/dist/migrations/mysqlEngine.js +11 -0
- package/dist/migrations/namespaceCharset.js +51 -0
- package/dist/migrations/planner.js +775 -0
- package/dist/migrations/postgresEnumCheck.js +19 -0
- package/dist/migrations/reactiveTriggerDrift.js +58 -0
- package/dist/migrations/reactiveTriggerName.js +7 -0
- package/dist/migrations/renameMssqlCheck.js +21 -0
- package/dist/migrations/restoreSnapshots.js +206 -0
- package/dist/migrations/resumeLedger.js +86 -0
- package/dist/migrations/rollingDeploy.js +106 -0
- package/dist/migrations/schemaOwner.js +46 -0
- package/dist/migrations/schemaOwnerTable.js +4 -0
- package/dist/migrations/softDropSnapshot.js +20 -0
- package/dist/migrations/sqlLimit.js +8 -0
- package/dist/migrations/sqliteConstraintIdentity.js +97 -0
- package/dist/migrations/sqliteDecimalBoot.js +22 -0
- package/dist/migrations/sqliteDecimalPublication.js +257 -0
- package/dist/migrations/sqliteDecimalReview.js +30 -0
- package/dist/migrations/sqliteDecimalReviewStore.js +188 -0
- package/dist/migrations/sqliteDecimalReviewWitness.js +38 -0
- package/dist/migrations/sqliteDecimalSource.js +69 -0
- package/dist/migrations/sqliteDecimalStage.js +135 -0
- package/dist/migrations/sqliteForeignKeys.js +38 -0
- package/dist/migrations/sqliteMigrationBatch.js +37 -0
- package/dist/migrations/sqliteRetainedSource.js +106 -0
- package/dist/migrations/sqliteRowIdentity.js +63 -0
- package/dist/migrations/sqliteSchemaEpoch.js +12 -0
- package/dist/migrations/sqliteSnapshotGuard.js +10 -0
- package/dist/migrations/sqliteTableContents.js +34 -0
- package/dist/migrations/sqliteTableCopy.js +25 -0
- package/dist/migrations/sqliteTableSchema.js +19 -0
- package/dist/migrations/sqliteWitnessProtection.js +8 -0
- package/dist/migrations/squash.js +81 -0
- package/dist/migrations/storeCharset.js +66 -0
- package/dist/migrations/transactionCause.js +10 -0
- package/dist/migrations/undeclaredCharset.js +40 -0
- package/dist/migrations/uniqueActiveLowering.js +39 -0
- package/dist/mixins.js +22 -0
- package/dist/mssqlDecimalDistinct.js +51 -0
- package/dist/mssqlJsonValueCheck.js +42 -0
- package/dist/mssqlTextStorage.js +9 -0
- package/dist/namespace.js +21 -0
- package/dist/{wire-CG2j7J3f.js → optimisticVersion.js} +2 -2
- package/dist/pendingAttribution.js +79 -0
- package/dist/pluginRef.js +17 -0
- package/dist/predicate.js +266 -0
- package/dist/predicateCoercion.js +13 -0
- package/dist/projectedColumn.js +37 -0
- package/dist/queryBuilder.js +382 -0
- package/dist/rawSql.js +8 -0
- package/dist/relations.js +69 -0
- package/dist/relationsRegistry.js +38 -0
- package/dist/replaceInProgress.js +53 -0
- package/dist/requiredColumns.js +26 -0
- package/dist/residency.js +52 -0
- package/dist/retention.js +62 -0
- package/dist/rowDecoder.js +123 -0
- package/dist/rowKey.js +8 -0
- package/dist/rowSchema.js +31 -0
- package/dist/scopeKey.js +8 -0
- package/dist/seed.js +33 -0
- package/dist/snowflake.js +26 -0
- package/dist/sql.d.ts +27 -6
- package/dist/sql.js +41 -6905
- package/dist/sqlCompiler.js +343 -0
- package/dist/sqlError.js +8 -0
- package/dist/sqlPoolCapacity.js +12 -0
- package/dist/sqliteDecimal.js +22 -0
- package/dist/sqliteDecimalAggregateSql.js +61 -0
- package/dist/sqliteDecimalColumnSql.js +108 -0
- package/dist/sqliteDecimalConversion.js +15 -0
- package/dist/sqliteDecimalCte.js +154 -0
- package/dist/sqliteDecimalEncodeSql.js +32 -0
- package/dist/sqliteDecimalOutputs.js +74 -0
- package/dist/sqliteDecimalPredicate.js +66 -0
- package/dist/sqliteDecimalQuery.js +183 -0
- package/dist/sqliteDecimalQueryPlan.js +37 -0
- package/dist/sqliteDecimalResultSql.js +44 -0
- package/dist/sqliteDecimalRowQuery.js +63 -0
- package/dist/sqliteDecimalSetQuery.js +50 -0
- package/dist/sqliteDecimalSource.js +63 -0
- package/dist/sqliteDecimalStorage.js +29 -0
- package/dist/sqliteDecimalSubquery.js +19 -0
- package/dist/sqliteDecimalWindowQuery.js +196 -0
- package/dist/sqliteValueCoercion.js +4 -0
- package/dist/stampId.js +21 -0
- package/dist/storeErrors.js +9 -0
- package/dist/storeRefusal.js +20 -0
- package/dist/stream.js +36 -0
- package/dist/subjectGraph.js +175 -0
- package/dist/table.js +394 -0
- package/dist/tableRegistry.js +16 -0
- package/dist/tenantSelection.js +27 -0
- package/dist/transactionOutcome.js +97 -0
- package/dist/transientContention.js +36 -0
- package/dist/typedStore.js +16 -0
- package/dist/upsertUpdate.js +20 -0
- package/dist/vectorEmbedding.js +31 -0
- package/dist/view.js +39 -0
- package/dist/wire.js +6 -2
- package/dist/writeAttribution.js +16 -0
- package/dist/writeRecorder.js +37 -0
- package/package.json +3 -3
- package/dist/replaceInProgress-DC88Pdiy.js +0 -3735
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
import { isSqliteFamily as e } from "../dialect.js";
|
|
2
|
+
import { isFrameworkOwnedLiveTable as t, isSoftDropSnapshotName as n, softDropSnapshotStamp as r } from "./frameworkLiveTables.js";
|
|
3
|
+
import { indexStorageShape as i } from "../indexStorage.js";
|
|
4
|
+
import { findCyclicFkBackEdges as a, fkEdgeKey as o } from "./fkCycles.js";
|
|
5
|
+
import { normalizeIndexPredicate as s } from "./indexPredicate.js";
|
|
6
|
+
import { canonicalIdColumn as c, comparableDefault as l, hasComparableType as u, isPgSequenceDefault as d, isSolePrimaryKeyId as f, isTextStorage as p, normRef as m, normalizeDefault as h, renderedTextWidth as g, resolvedIdStorage as _, sameIdSpelling as v, sameSpatial as y } from "./columnEquivalence.js";
|
|
7
|
+
import { fingerprintSchema as b } from "./fingerprint.js";
|
|
8
|
+
import { charsetColumnsOf as x, columnTakesCharset as S } from "./storeCharset.js";
|
|
9
|
+
import { declaredSnapshot as C } from "./declaredSchema.js";
|
|
10
|
+
//#region src/migrations/planner.ts
|
|
11
|
+
var w = {
|
|
12
|
+
postgres: !0,
|
|
13
|
+
mysql: !0,
|
|
14
|
+
mariadb: !0,
|
|
15
|
+
mssql: !0,
|
|
16
|
+
sqlite: !0
|
|
17
|
+
}, T = {
|
|
18
|
+
postgres: !0,
|
|
19
|
+
mysql: !1,
|
|
20
|
+
mariadb: !1,
|
|
21
|
+
mssql: !0,
|
|
22
|
+
sqlite: !0
|
|
23
|
+
}, E = (e) => {
|
|
24
|
+
switch (e) {
|
|
25
|
+
case "add-column":
|
|
26
|
+
case "drop-column":
|
|
27
|
+
case "rename-column":
|
|
28
|
+
case "alter-column-nullability":
|
|
29
|
+
case "alter-column-type":
|
|
30
|
+
case "add-foreign-key":
|
|
31
|
+
case "add-unique":
|
|
32
|
+
case "add-unique-composite":
|
|
33
|
+
case "add-check":
|
|
34
|
+
case "convert-charset": return T;
|
|
35
|
+
default: return w;
|
|
36
|
+
}
|
|
37
|
+
}, ee = (e, t) => {
|
|
38
|
+
let n = new Map(t.tables.map((e) => [e.name, e]));
|
|
39
|
+
return new Map(e.map((e) => [e.tableName, {
|
|
40
|
+
table: e,
|
|
41
|
+
snapshot: n.get(e.tableName),
|
|
42
|
+
columnDefs: e.fields
|
|
43
|
+
}]));
|
|
44
|
+
}, D = (e) => e.type === "id" && e.idScheme?.kind === "numeric", O = (e, t) => {
|
|
45
|
+
let n = h(e), r = h(t);
|
|
46
|
+
if (n === r) return !0;
|
|
47
|
+
let i = d;
|
|
48
|
+
return e.type === "id" && t.type === "id" && (D(e) && n === null && i(t) || D(t) && r === null && i(e)) ? !0 : l(e) === l(t);
|
|
49
|
+
}, k = (e, t) => {
|
|
50
|
+
let n = e && e.length > 0 ? e : void 0, r = t && t.length > 0 ? t : void 0;
|
|
51
|
+
if (!n && !r) return !0;
|
|
52
|
+
if (!n || !r || n.length !== r.length) return !1;
|
|
53
|
+
if (n.length >= 32 && j(n) && j(r)) {
|
|
54
|
+
let e = /* @__PURE__ */ new Map();
|
|
55
|
+
for (let t of n) e.set(t, (e.get(t) ?? 0) + 1);
|
|
56
|
+
for (let t of r) {
|
|
57
|
+
let n = e.get(t);
|
|
58
|
+
if (n === void 0) return !1;
|
|
59
|
+
n === 1 ? e.delete(t) : e.set(t, n - 1);
|
|
60
|
+
}
|
|
61
|
+
return e.size === 0;
|
|
62
|
+
}
|
|
63
|
+
let i = [...n].sort(), a = [...r].sort();
|
|
64
|
+
return i.every((e, t) => e === a[t]);
|
|
65
|
+
}, A = (t, n, r, i) => r === void 0 || e(r) ? !0 : t.type !== "text" || !p(n) || g(t, r, i) === n.maxLength, j = (e) => {
|
|
66
|
+
for (let t of [
|
|
67
|
+
"includes",
|
|
68
|
+
"every",
|
|
69
|
+
Symbol.iterator
|
|
70
|
+
]) {
|
|
71
|
+
let n = Object.getOwnPropertyDescriptor(e, t);
|
|
72
|
+
if (n !== void 0 && !("value" in n)) return !1;
|
|
73
|
+
}
|
|
74
|
+
if (Object.getPrototypeOf(e) !== Array.prototype || e.includes !== Array.prototype.includes || e.every !== Array.prototype.every || e[Symbol.iterator] !== Array.prototype[Symbol.iterator]) return !1;
|
|
75
|
+
for (let t = 0; t < e.length; t++) {
|
|
76
|
+
let n = Object.getOwnPropertyDescriptor(e, t);
|
|
77
|
+
if (n === void 0 || !("value" in n) || typeof n.value != "string") return !1;
|
|
78
|
+
}
|
|
79
|
+
return !0;
|
|
80
|
+
}, M = (e, t) => {
|
|
81
|
+
if (e.length !== t.length) return !1;
|
|
82
|
+
let n = e.length >= 32 && j(e) && j(t) ? new Set(t) : void 0;
|
|
83
|
+
return e.every((e) => n === void 0 ? t.includes(e) : n.has(e));
|
|
84
|
+
}, N = (e) => {
|
|
85
|
+
for (let t of ["find", Symbol.iterator]) {
|
|
86
|
+
let n = Object.getOwnPropertyDescriptor(e, t);
|
|
87
|
+
if (n !== void 0 && !("value" in n)) return;
|
|
88
|
+
}
|
|
89
|
+
if (e.length < 32 || Object.getPrototypeOf(e) !== Array.prototype || e.find !== Array.prototype.find || e[Symbol.iterator] !== Array.prototype[Symbol.iterator]) return;
|
|
90
|
+
let t = /* @__PURE__ */ new Map();
|
|
91
|
+
for (let n = 0; n < e.length; n++) {
|
|
92
|
+
let r = Object.getOwnPropertyDescriptor(e, n);
|
|
93
|
+
if (r === void 0 || !("value" in r) || typeof r.value != "object" || r.value === null || Object.values(Object.getOwnPropertyDescriptors(r.value)).some((e) => !("value" in e))) return;
|
|
94
|
+
let i = Object.getOwnPropertyDescriptor(r.value, "name");
|
|
95
|
+
if (i === void 0 || !("value" in i) || typeof i.value != "string") return;
|
|
96
|
+
t.has(i.value) || t.set(i.value, r.value);
|
|
97
|
+
}
|
|
98
|
+
return t;
|
|
99
|
+
}, P = (e, t, n, r, i = !1) => (!u(e) || e.type === t.type || v(e, t, i)) && c(e, i).idStorage === c(t, !1).idStorage && e.nullable === t.nullable && (e.unique === t.unique || v(e, t, i)) && e.jsonValidation === t.jsonValidation && (!u(e) || e.sqliteDecimalEncoding === t.sqliteDecimalEncoding) && (t.enumValues === void 0 || e.enumValues === void 0 || M(e.enumValues, t.enumValues)) && (t.precision === void 0 || e.precision === t.precision && (e.scale ?? 0) === (t.scale ?? 0)) && y(e.spatial, t.spatial) && (e.storageType !== void 0 || t.storageType !== void 0 || t.arrayElement === void 0 || e.arrayElement === void 0 || e.arrayElement === t.arrayElement) && (t.vectorDim === void 0 || e.vectorDim === t.vectorDim) && (e.vectorPrecision ?? "float32") === (t.vectorPrecision ?? "float32") && O(e, t) && A(e, t, n, r) && (e.type !== "reference" || (e.referenceStorage ?? "text") === (t.referenceStorage ?? "text")) && JSON.stringify(m(e.references)) === JSON.stringify(m(t.references)) && k(e.oneOf, t.oneOf), F = (e, t) => t.oneOf && t.oneOf.length > 0 ? L({
|
|
100
|
+
kind: "add-check",
|
|
101
|
+
table: e,
|
|
102
|
+
column: t.name,
|
|
103
|
+
values: t.oneOf
|
|
104
|
+
}, "safe", `enum-membership CHECK on ${e}.${t.name}`) : void 0, I = (e, t) => e.unique === t.unique && e.valid !== !1 && t.valid !== !1 && s(e.where) === s(t.where) && JSON.stringify(i(e.kind, e.kindOptions)) === JSON.stringify(i(t.kind, t.kindOptions)) && (e.expression ?? !1) === (t.expression ?? !1) && (e.expression === !0 && t.expression === !0 || e.columns.length === t.columns.length && e.columns.every((e, n) => e === t.columns[n])), te = (e, t, n) => {
|
|
105
|
+
let r = new Map(t.columns.map((e) => [e.name, e])), i = new Map(e.columns.map((e) => [e.name, e])), a = [], o = [], s = [], c = new Set(e.indexes.flatMap((e) => e.columns));
|
|
106
|
+
for (let t of e.columns) {
|
|
107
|
+
let i = r.get(t.name);
|
|
108
|
+
i ? P(t, i, n, c.has(t.name) || t.unique, f(e, t)) || s.push({
|
|
109
|
+
declared: t,
|
|
110
|
+
live: i
|
|
111
|
+
}) : a.push(t);
|
|
112
|
+
}
|
|
113
|
+
for (let e of t.columns) i.has(e.name) || o.push(e);
|
|
114
|
+
return {
|
|
115
|
+
added: a,
|
|
116
|
+
removed: o,
|
|
117
|
+
changed: s
|
|
118
|
+
};
|
|
119
|
+
}, ne = (e, t, n, r) => {
|
|
120
|
+
let i = (e) => n === "turso" || n === "sqlite" ? e.toLowerCase() : e, a = r.size > 0 ? /* @__PURE__ */ new Map() : void 0;
|
|
121
|
+
if (a !== void 0) for (let e of t.indexes) e.unique && !e.expression && e.where === void 0 && e.columns.some((e) => r.has(e)) && a.set(e, {
|
|
122
|
+
...e,
|
|
123
|
+
columns: e.columns.map((e) => r.get(e) ?? e)
|
|
124
|
+
});
|
|
125
|
+
let o = new Map(t.indexes.map((e) => [i(e.name), a?.get(e) ?? e])), s = new Map(e.indexes.map((e) => [i(e.name), e])), c = [], l = [];
|
|
126
|
+
for (let t of e.indexes) {
|
|
127
|
+
let e = o.get(i(t.name));
|
|
128
|
+
(!e || !I(t, e)) && c.push(t);
|
|
129
|
+
}
|
|
130
|
+
for (let e of t.indexes) {
|
|
131
|
+
let t = s.get(i(e.name));
|
|
132
|
+
(!t || !I(t, a?.get(e) ?? e)) && l.push(e);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
added: c,
|
|
136
|
+
removed: l
|
|
137
|
+
};
|
|
138
|
+
}, L = (e, t, n, r) => ({
|
|
139
|
+
op: e,
|
|
140
|
+
classification: t,
|
|
141
|
+
atomicByDialect: E(e.kind),
|
|
142
|
+
...n ? { reason: n } : {},
|
|
143
|
+
...r ? { blocked: r } : {}
|
|
144
|
+
}), R = (e, t, n, r) => {
|
|
145
|
+
let i = {
|
|
146
|
+
kind: "add-column",
|
|
147
|
+
table: e,
|
|
148
|
+
column: t
|
|
149
|
+
};
|
|
150
|
+
return t.nullable ? L(i, "safe", "nullable column add — no backfill needed") : D(t) && (r === "postgres" || r === "mysql" || r === "mariadb") ? L(i, "needs-default", "numeric id — the auto-increment numbers every existing row") : t.hasDefault && t.defaultValue !== void 0 ? L(i, "needs-default", "NOT NULL with literal default — DDL DEFAULT will populate existing rows") : n?.backfill ? L(i, "needs-backfill", `backfill declared (kind=${n.backfill.kind}) — applier will run 3-step add/update/set-not-null`) : L(i, "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` });
|
|
151
|
+
}, z = (e, t, n) => {
|
|
152
|
+
let r = {
|
|
153
|
+
kind: "drop-column",
|
|
154
|
+
table: e,
|
|
155
|
+
column: t.name
|
|
156
|
+
};
|
|
157
|
+
return n?.dropped ? L(r, "lossy", `column dropped via \`${t.name}: dropped()\` marker — intentional`) : L(r, "lossy", "column missing from declared schema", { fix: `if intentional, add \`${t.name}: dropped()\` to the schema. If a typo, restore the field` });
|
|
158
|
+
}, B = (e, t, n, r, i) => L({
|
|
159
|
+
kind: "create-table",
|
|
160
|
+
table: e,
|
|
161
|
+
columns: t,
|
|
162
|
+
indexes: n,
|
|
163
|
+
...r && r.length > 0 ? { primaryKey: r } : {}
|
|
164
|
+
}, "safe", i ?? "new table — no data to preserve"), V = (e) => L({
|
|
165
|
+
kind: "drop-table",
|
|
166
|
+
table: e
|
|
167
|
+
}, "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" }), H = 5e4, re = (e, t, n, r) => {
|
|
168
|
+
let i = (n ?? 0) >= r, a;
|
|
169
|
+
return a = !t.unique || t.where !== void 0 ? {
|
|
170
|
+
kind: "add-index",
|
|
171
|
+
table: e,
|
|
172
|
+
index: t
|
|
173
|
+
} : t.columns.length === 1 ? {
|
|
174
|
+
kind: "add-unique",
|
|
175
|
+
table: e,
|
|
176
|
+
column: t.columns[0]
|
|
177
|
+
} : {
|
|
178
|
+
kind: "add-unique-composite",
|
|
179
|
+
table: e,
|
|
180
|
+
name: t.name,
|
|
181
|
+
fields: t.columns
|
|
182
|
+
}, i ? L(a, "online-required", `online build on ${n} rows — CONCURRENTLY / INPLACE / WITH(ONLINE=ON) depending on dialect`) : L(a, "safe", t.where === void 0 ? t.unique ? t.columns.length === 1 ? "add unique constraint" : "add composite UNIQUE constraint" : `add ${t.kind === void 0 || t.kind === "btree" ? "B-tree" : t.kind.toUpperCase()} index` : `add ${t.unique ? "unique " : ""}partial index`);
|
|
183
|
+
}, ie = (e, t) => {
|
|
184
|
+
let n = t.name === `${e}_pkey`;
|
|
185
|
+
return t.unique && t.where === void 0 && (t.columns.length > 1 || n) ? L({
|
|
186
|
+
kind: "drop-unique-composite",
|
|
187
|
+
table: e,
|
|
188
|
+
name: t.name
|
|
189
|
+
}, "safe", "drop composite UNIQUE constraint (no data loss — constraint is derived metadata)") : L({
|
|
190
|
+
kind: "drop-index",
|
|
191
|
+
table: e,
|
|
192
|
+
index: t.name
|
|
193
|
+
}, "safe", "drop index (no data loss — index is derived data)");
|
|
194
|
+
}, U = (t, n) => {
|
|
195
|
+
let r = t.type;
|
|
196
|
+
if (r === "id") return _(t) === "numeric" ? "bigint" : "text";
|
|
197
|
+
if (r === "array") return n === void 0 || n === "postgres" ? "array" : n === "mysql" || n === "mariadb" ? "json" : "text";
|
|
198
|
+
if (r === "enum") return n === "mssql" || e(n ?? "postgres") ? "text" : "enum";
|
|
199
|
+
if (r === "reference") return t.referenceStorage === "numeric" ? "bigint" : "text";
|
|
200
|
+
switch (n) {
|
|
201
|
+
case "sqlite":
|
|
202
|
+
case "turso": return r === "timestamp" || r === "date" || r === "json" ? "text" : r === "boolean" || r === "bigint" ? "integer" : r;
|
|
203
|
+
case "mssql": return r === "json" ? "text" : r;
|
|
204
|
+
default: return r;
|
|
205
|
+
}
|
|
206
|
+
}, W = (e, t, n) => {
|
|
207
|
+
let r = `acknowledge it on the column: \`.narrowedFrom('${e}')\``;
|
|
208
|
+
return n === "postgres" ? `${r} — add \`{ using: '${t.name}::${t.type}' }\` when the cast is not implicit, or use a file-based migration` : `${r} — ${n === void 0 ? "this dialect" : n} converts in place (\`using\` is postgres-only and is ignored here), so verify the conversion is value-preserving for your data, or use a file-based migration`;
|
|
209
|
+
}, G = (t, n, r, i, a, o) => {
|
|
210
|
+
let s = [], c = u(n) && (n.sqliteDecimalEncoding !== void 0 || r.sqliteDecimalEncoding !== void 0) && (n.sqliteDecimalEncoding !== r.sqliteDecimalEncoding || n.precision !== r.precision || (n.scale ?? 0) !== (r.scale ?? 0) || n.type !== r.type);
|
|
211
|
+
if (c && s.push(L({
|
|
212
|
+
kind: "alter-column-type",
|
|
213
|
+
table: t,
|
|
214
|
+
column: n.name,
|
|
215
|
+
from: r.type,
|
|
216
|
+
to: n.type
|
|
217
|
+
}, "multi-step", `physical decimal representation changes on ${t}.${n.name}; encoded keys require explicit data conversion, not a SQL type cast`, { fix: "Use a verified data migration that converts every value and preserves precision, defaults and constraints. Previously rounded NUMERIC values cannot recover their original digits." })), n.nullable !== r.nullable) {
|
|
218
|
+
let e = {
|
|
219
|
+
kind: "alter-column-nullability",
|
|
220
|
+
table: t,
|
|
221
|
+
column: n.name,
|
|
222
|
+
toNullable: n.nullable
|
|
223
|
+
};
|
|
224
|
+
s.push(n.nullable ? L(e, "safe", `loosen ${t}.${n.name} to nullable`) : L(e, "needs-backfill", `tighten ${t}.${n.name} to NOT NULL — existing rows must already be non-null`));
|
|
225
|
+
}
|
|
226
|
+
if (n.unique !== r.unique) {
|
|
227
|
+
if (n.unique) {
|
|
228
|
+
let e = {
|
|
229
|
+
kind: "add-unique",
|
|
230
|
+
table: t,
|
|
231
|
+
column: n.name
|
|
232
|
+
}, r = (a ?? 0) >= o;
|
|
233
|
+
s.push(r ? L(e, "online-required", `online unique build on ${a} rows — CONCURRENTLY / INPLACE / WITH(ONLINE=ON) depending on dialect`) : L(e, "safe", `add unique constraint on ${t}.${n.name} — existing rows must already be distinct (else the DB rejects at migrate)`));
|
|
234
|
+
} else s.push(L({
|
|
235
|
+
kind: "drop-unique",
|
|
236
|
+
table: t,
|
|
237
|
+
column: n.name
|
|
238
|
+
}, "safe", `drop unique constraint on ${t}.${n.name} — constraint is derived metadata, no data loss`));
|
|
239
|
+
}
|
|
240
|
+
if (!c && u(n) && U(n, i) !== U(r, i)) {
|
|
241
|
+
let e = n.narrowedFrom;
|
|
242
|
+
if (r.type === "integer" && n.type === "bigint" && e === void 0) s.push(L({
|
|
243
|
+
kind: "alter-column-type",
|
|
244
|
+
table: t,
|
|
245
|
+
column: n.name,
|
|
246
|
+
from: r.type,
|
|
247
|
+
to: n.type
|
|
248
|
+
}, "safe", `integer range widens to bigint on ${t}.${n.name} — every stored integer remains representable`));
|
|
249
|
+
else if (e && e.from === r.type) {
|
|
250
|
+
let i = {
|
|
251
|
+
kind: "alter-column-type",
|
|
252
|
+
table: t,
|
|
253
|
+
column: n.name,
|
|
254
|
+
from: r.type,
|
|
255
|
+
to: n.type,
|
|
256
|
+
...e.using === void 0 ? {} : { using: e.using }
|
|
257
|
+
};
|
|
258
|
+
s.push(L(i, "needs-backfill", `type change ${r.type} → ${n.type} on ${t}.${n.name} — narrowedFrom('${e.from}') declared` + (e.using ? ` with USING ${e.using}` : " (implicit cast)")));
|
|
259
|
+
} else {
|
|
260
|
+
let e = {
|
|
261
|
+
kind: "alter-column-type",
|
|
262
|
+
table: t,
|
|
263
|
+
column: n.name,
|
|
264
|
+
from: r.type,
|
|
265
|
+
to: n.type
|
|
266
|
+
};
|
|
267
|
+
s.push(L(e, "lossy", `type change ${r.type} → ${n.type} on ${t}.${n.name} may not be value-preserving`, { fix: W(r.type, n, i) }));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (n.type === "text" && p(r) && !e(i ?? "postgres") && n.maxLength !== r.maxLength) {
|
|
271
|
+
let e = n.maxLength === void 0 || r.maxLength !== void 0 && n.maxLength > r.maxLength, i = r.maxLength === void 0 ? "unbounded" : `varchar(${r.maxLength})`, a = n.maxLength === void 0 ? "unbounded" : `varchar(${n.maxLength})`;
|
|
272
|
+
s.push(L({
|
|
273
|
+
kind: "alter-column-type",
|
|
274
|
+
table: t,
|
|
275
|
+
column: n.name,
|
|
276
|
+
from: "text",
|
|
277
|
+
to: "text"
|
|
278
|
+
}, e ? "safe" : "needs-backfill", e ? `text length ${i} → ${a} on ${t}.${n.name} — widening, no data can be lost` : `text length ${i} → ${a} on ${t}.${n.name} — NARROWING; the ALTER fails if any existing value is longer than ${String(n.maxLength)}. Check first: SELECT COUNT(*) FROM ${t} WHERE LENGTH(${n.name}) > ${String(n.maxLength)}`));
|
|
279
|
+
}
|
|
280
|
+
if (n.type === "enum" && r.type === "enum" && r.enumValues !== void 0 && n.enumValues !== void 0) {
|
|
281
|
+
let e = new Set(r.enumValues);
|
|
282
|
+
new Set(n.enumValues);
|
|
283
|
+
for (let r of n.enumValues) e.has(r) || s.push(L({
|
|
284
|
+
kind: "add-enum-value",
|
|
285
|
+
table: t,
|
|
286
|
+
column: n.name,
|
|
287
|
+
value: r,
|
|
288
|
+
...n.enumName === void 0 ? {} : { enumName: n.enumName }
|
|
289
|
+
}, "safe", `add '${r}' to the enum on ${t}.${n.name} — existing rows are untouched, and the label is what the declaration already promises`));
|
|
290
|
+
}
|
|
291
|
+
if (!y(n.spatial, r.spatial)) {
|
|
292
|
+
let e = n.spatial, i = r.spatial, a = (e) => e === void 0 ? "plain text" : `${e.kind}(${e.geomKind},${String(e.srid)})`, o = e !== void 0 && i !== void 0 && e.kind === i.kind && e.geomKind.toUpperCase() === i.geomKind.toUpperCase() && e.srid !== i.srid, c = e === void 0 ? void 0 : e.kind === "geography" ? `ST_Transform("${n.name}"::geometry, ${String(e.srid)})::geography` : `ST_Transform("${n.name}", ${String(e.srid)})`, l = n.narrowedFrom, u = l !== void 0 && l.from === r.type, d = o ? c : u ? l.using : void 0;
|
|
293
|
+
s.push(L({
|
|
294
|
+
kind: "alter-column-type",
|
|
295
|
+
table: t,
|
|
296
|
+
column: n.name,
|
|
297
|
+
from: r.type,
|
|
298
|
+
to: n.type,
|
|
299
|
+
...d === void 0 ? {} : { using: d }
|
|
300
|
+
}, o || u ? "needs-backfill" : "lossy", o ? `SRID ${String(i.srid)} → ${String(e.srid)} on ${t}.${n.name} — every value is reprojected with ST_Transform; the rewrite touches the whole table` : u ? `spatial type ${a(i)} → ${a(e)} on ${t}.${n.name} — narrowedFrom('${l.from}') declared` + (l.using ? ` with USING ${l.using}` : " (implicit cast)") : `spatial type ${a(i)} → ${a(e)} on ${t}.${n.name} — no transform preserves this; a point is not a polygon, and text is not known to hold geometry at all`, o || u ? void 0 : { fix: `acknowledge it on the column: \`.narrowedFrom('${r.type}')\` — add \`{ using: 'ST_…(${n.name})' }\` with the PostGIS expression that converts your values (the ALTER fails at the database if any existing value does not fit ${a(e)}), or use a file-based migration` }));
|
|
301
|
+
}
|
|
302
|
+
if (n.type === "array" && r.type === "array" && r.arrayElement !== void 0 && n.arrayElement !== void 0 && n.arrayElement !== r.arrayElement) {
|
|
303
|
+
let e = n.narrowedFrom, i = e !== void 0 && e.from === r.type;
|
|
304
|
+
s.push(L({
|
|
305
|
+
kind: "alter-column-type",
|
|
306
|
+
table: t,
|
|
307
|
+
column: n.name,
|
|
308
|
+
from: r.type,
|
|
309
|
+
to: n.type,
|
|
310
|
+
...i && e.using !== void 0 ? { using: e.using } : {}
|
|
311
|
+
}, i ? "needs-backfill" : "lossy", i ? `array element ${r.arrayElement} → ${n.arrayElement} on ${t}.${n.name} — narrowedFrom('${e.from}') declared` + (e.using ? ` with USING ${e.using}` : " (implicit cast)") : `array element ${r.arrayElement} → ${n.arrayElement} on ${t}.${n.name} — the engine will not choose a cast for you, and a value that does not parse fails the ALTER`, i ? void 0 : { fix: `acknowledge it on the column: \`.narrowedFrom('${r.type}')\` — add \`{ using: '"${n.name}"::${n.arrayElement}[]' }\` or the expression your values need, or use a file-based migration` }));
|
|
312
|
+
}
|
|
313
|
+
if (n.type === "vector" && r.type === "vector" && (r.vectorDim !== void 0 && n.vectorDim !== r.vectorDim || (n.vectorPrecision ?? "float32") !== (r.vectorPrecision ?? "float32"))) {
|
|
314
|
+
let e = (e) => `${e.vectorPrecision === "half" ? "halfvec" : "vector"}(${String(e.vectorDim ?? "?")})`, i = n.narrowedFrom, a = i !== void 0 && i.from === r.type;
|
|
315
|
+
s.push(L({
|
|
316
|
+
kind: "alter-column-type",
|
|
317
|
+
table: t,
|
|
318
|
+
column: n.name,
|
|
319
|
+
from: r.type,
|
|
320
|
+
to: n.type,
|
|
321
|
+
...a && i.using !== void 0 ? { using: i.using } : {}
|
|
322
|
+
}, a ? "needs-backfill" : "lossy", a ? `vector ${e(r)} → ${e(n)} on ${t}.${n.name} — narrowedFrom('${i.from}') declared` + (i.using ? ` with USING ${i.using}` : " (implicit cast)") : `vector ${e(r)} → ${e(n)} on ${t}.${n.name} — an embedding of one dimension is not an embedding of another, and halving the precision discards bits. Existing rows cannot be carried across by the engine.`, a ? void 0 : { fix: `acknowledge it on the column: \`.narrowedFrom('${r.type}')\` — add \`{ using: '…' }\` with the expression that converts your vectors (re-embedding is usually a backfill, not a cast), or use a file-based migration` }));
|
|
323
|
+
}
|
|
324
|
+
if (!c && n.type === "decimal" && r.type === "decimal" && r.precision !== void 0 && (n.precision !== r.precision || (n.scale ?? 0) !== (r.scale ?? 0))) {
|
|
325
|
+
let e = (e) => `${String(e.precision)},${String(e.scale ?? 0)}`, i = n.precision !== void 0 && r.precision !== void 0 && n.precision - (n.scale ?? 0) >= r.precision - (r.scale ?? 0) && (n.scale ?? 0) >= (r.scale ?? 0);
|
|
326
|
+
s.push(L({
|
|
327
|
+
kind: "alter-column-type",
|
|
328
|
+
table: t,
|
|
329
|
+
column: n.name,
|
|
330
|
+
from: "decimal",
|
|
331
|
+
to: "decimal"
|
|
332
|
+
}, i ? "safe" : "needs-backfill", i ? `decimal(${e(r)}) → decimal(${e(n)}) on ${t}.${n.name} — widening, every value survives` : `decimal(${e(r)}) → decimal(${e(n)}) on ${t}.${n.name} — NARROWING; the engine ROUNDS to the new scale and rejects a value that no longer fits. Check first: SELECT COUNT(*) FROM ${t} WHERE ${n.name} <> ROUND(${n.name}, ${String(n.scale ?? 0)})`));
|
|
333
|
+
}
|
|
334
|
+
if (!O(n, r)) {
|
|
335
|
+
let e = {
|
|
336
|
+
kind: "alter-column-default",
|
|
337
|
+
table: t,
|
|
338
|
+
column: n.name,
|
|
339
|
+
hasDefault: n.hasDefault,
|
|
340
|
+
...n.defaultValue === void 0 ? {} : { defaultValue: n.defaultValue }
|
|
341
|
+
};
|
|
342
|
+
s.push(L(e, "safe", `default change on ${t}.${n.name} affects only future inserts`));
|
|
343
|
+
}
|
|
344
|
+
if (i === "mssql" && n.jsonValidation !== void 0 && n.jsonValidation !== "unverified" && n.jsonValidation !== r.jsonValidation) {
|
|
345
|
+
let e = n.jsonValidation === "value" && r.jsonValidation === "container";
|
|
346
|
+
s.push(L({
|
|
347
|
+
kind: "reconcile-json-check",
|
|
348
|
+
table: t,
|
|
349
|
+
column: n.name,
|
|
350
|
+
validation: n.jsonValidation,
|
|
351
|
+
previousValidation: r.jsonValidation ?? "absent",
|
|
352
|
+
dropConstraints: (r.jsonChecks ?? []).map((e) => e.name).sort()
|
|
353
|
+
}, e ? "safe" : "needs-backfill", e ? `allow every JSON value on ${t}.${n.name}; preserve existing rows` : `validate existing JSON on ${t}.${n.name}; invalid rows refuse the migration without being rewritten`));
|
|
354
|
+
}
|
|
355
|
+
if (!k(n.oneOf, r.oneOf)) {
|
|
356
|
+
r.oneOf && r.oneOf.length > 0 && s.push(L({
|
|
357
|
+
kind: "drop-check",
|
|
358
|
+
table: t,
|
|
359
|
+
column: n.name
|
|
360
|
+
}, "safe", `drop stale enum CHECK on ${t}.${n.name}`));
|
|
361
|
+
let e = F(t, n);
|
|
362
|
+
e && s.push(e);
|
|
363
|
+
}
|
|
364
|
+
return JSON.stringify(m(n.references)) !== JSON.stringify(m(r.references)) && (r.references && s.push(L({
|
|
365
|
+
kind: "drop-foreign-key",
|
|
366
|
+
table: t,
|
|
367
|
+
column: n.name
|
|
368
|
+
}, "safe", `drop FK on ${t}.${n.name} (→ ${r.references.table})`)), s.push(...K(t, n))), s;
|
|
369
|
+
}, K = (e, t) => {
|
|
370
|
+
let n = [];
|
|
371
|
+
if (!t.references) return n;
|
|
372
|
+
let r = t.references.table, i = t.references.orphanPolicy ?? "fail", a = {
|
|
373
|
+
kind: "add-foreign-key",
|
|
374
|
+
table: e,
|
|
375
|
+
column: t.name,
|
|
376
|
+
targetTable: r,
|
|
377
|
+
targetColumn: t.references.column,
|
|
378
|
+
...t.references.onDelete ? { onDelete: t.references.onDelete } : {},
|
|
379
|
+
...t.references.onUpdate ? { onUpdate: t.references.onUpdate } : {}
|
|
380
|
+
};
|
|
381
|
+
return i === "null" && !t.nullable ? n.push(L(a, "lossy", `add FK ${e}.${t.name} → ${r}: 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` })) : i === "null" || i === "delete" ? n.push(L({
|
|
382
|
+
...a,
|
|
383
|
+
orphanPolicy: i
|
|
384
|
+
}, "lossy", i === "delete" ? `add FK ${e}.${t.name} → ${r}: orphanPolicy 'delete' first DELETES rows whose ${t.name} points at a missing ${r}` : `add FK ${e}.${t.name} → ${r}: orphanPolicy 'null' first NULLs ${t.name} where it points at a missing ${r}`)) : n.push(L(a, "needs-backfill", `add FK ${e}.${t.name} → ${r} — existing rows must already reference a valid ${r} (else set orphanPolicy:'null'/'delete')`)), n;
|
|
385
|
+
}, ae = (e, t, n) => {
|
|
386
|
+
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));
|
|
387
|
+
for (let [n, c] of Object.entries(t)) {
|
|
388
|
+
let t = c.renamedFrom;
|
|
389
|
+
t && o.has(t) && s.has(n) && (i.add(n), a.add(t), r.push(L({
|
|
390
|
+
kind: "rename-column",
|
|
391
|
+
table: e.tableName,
|
|
392
|
+
from: t,
|
|
393
|
+
to: n
|
|
394
|
+
}, "safe", `rename via \`.renamedFrom('${t}')\``)));
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
ops: r,
|
|
398
|
+
consumedAddedNames: i,
|
|
399
|
+
consumedRemovedNames: a
|
|
400
|
+
};
|
|
401
|
+
}, q = /* @__PURE__ */ new Set([
|
|
402
|
+
"postgres",
|
|
403
|
+
"mysql",
|
|
404
|
+
"mariadb",
|
|
405
|
+
"mssql"
|
|
406
|
+
]), J = {
|
|
407
|
+
ops: [],
|
|
408
|
+
consumedAddedNames: /* @__PURE__ */ new Set(),
|
|
409
|
+
consumedRemovedNames: /* @__PURE__ */ new Set()
|
|
410
|
+
}, Y = (e) => !e.unique && !e.expression && e.where === void 0 && (e.kind === void 0 || e.kind === "btree"), oe = (e, t, n, r, i) => {
|
|
411
|
+
if (i === void 0 || !q.has(i)) return J;
|
|
412
|
+
let a = t.added.filter((e) => Y(e) && !r.has(e.name));
|
|
413
|
+
if (a.length === 0) return J;
|
|
414
|
+
let o = (e) => e.columns.map((e) => n.get(e) ?? e).join("\0"), s = (e) => e.columns.join("\0"), c = new Set(t.added.map((e) => e.name)), l = /* @__PURE__ */ new Map();
|
|
415
|
+
for (let e of a) {
|
|
416
|
+
let t = s(e), n = l.get(t);
|
|
417
|
+
n === void 0 ? l.set(t, [e]) : n.push(e);
|
|
418
|
+
}
|
|
419
|
+
let u = [], d = /* @__PURE__ */ new Set(), f = /* @__PURE__ */ new Set();
|
|
420
|
+
for (let n of t.removed) {
|
|
421
|
+
if (!Y(n) || c.has(n.name)) continue;
|
|
422
|
+
let t = o(n), r = l.get(t);
|
|
423
|
+
if (r === void 0 || r.length !== 1) continue;
|
|
424
|
+
let i = r[0];
|
|
425
|
+
d.has(i.name) || i.name !== n.name && (d.add(i.name), f.add(n.name), u.push(L({
|
|
426
|
+
kind: "rename-index",
|
|
427
|
+
table: e,
|
|
428
|
+
from: n.name,
|
|
429
|
+
to: i.name
|
|
430
|
+
}, "safe", "rename index in place — catalog-only, no rebuild")));
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
ops: u,
|
|
434
|
+
consumedAddedNames: d,
|
|
435
|
+
consumedRemovedNames: f
|
|
436
|
+
};
|
|
437
|
+
}, se = (e, t, n) => {
|
|
438
|
+
let r = [], i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Set(), o = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Set();
|
|
439
|
+
for (let t of e.tables) t.renamedFrom !== void 0 && (s.set(t.renamedFrom, (s.get(t.renamedFrom) ?? 0) + 1), c.add(t.renamedFrom));
|
|
440
|
+
for (let c of e.tables) {
|
|
441
|
+
let e = c.renamedFrom;
|
|
442
|
+
e !== void 0 && e !== c.name && t.has(e) && (n.has(e) ? o.set(c.name, `.renamedFrom('${e}') NOT applied — '${e}' is still declared by this schema, so it belongs to that declaration. This table is created empty; that is the intended outcome when an app owns a table of the same name.`) : t.has(c.name) ? r.push(L({
|
|
443
|
+
kind: "rename-table",
|
|
444
|
+
from: e,
|
|
445
|
+
to: c.name
|
|
446
|
+
}, "needs-rename-annotation", `.renamedFrom('${e}') cannot be applied — both '${e}' and '${c.name}' exist in the database`, { fix: `decide which holds the real rows: move them into '${c.name}' and drop '${e}', or drop the empty '${c.name}' so the rename can run. '${e}' is left untouched until you do.` })) : (s.get(e) ?? 0) === 1 ? (i.set(c.name, e), a.add(e), r.push(L({
|
|
447
|
+
kind: "rename-table",
|
|
448
|
+
from: e,
|
|
449
|
+
to: c.name
|
|
450
|
+
}, "safe", `rename via \`.renamedFrom('${e}')\` — catalog-only, the rows stay put`))) : r.push(L({
|
|
451
|
+
kind: "rename-table",
|
|
452
|
+
from: e,
|
|
453
|
+
to: c.name
|
|
454
|
+
}, "needs-rename-annotation", `.renamedFrom('${e}') is claimed by ${s.get(e)} tables — nothing says which receives its rows`, { fix: `remove the \`.renamedFrom('${e}')\` marker from all but one of them.` })));
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
ops: r,
|
|
458
|
+
renamedInto: i,
|
|
459
|
+
renamedFromNames: a,
|
|
460
|
+
claimedOldNames: c,
|
|
461
|
+
refusals: o
|
|
462
|
+
};
|
|
463
|
+
}, ce = (e, t, n, r) => {
|
|
464
|
+
if (t === void 0) return {
|
|
465
|
+
live: e,
|
|
466
|
+
ops: []
|
|
467
|
+
};
|
|
468
|
+
let i = r !== void 0 && q.has(r), a = [], o = e.indexes.map((e) => {
|
|
469
|
+
if (!e.name.startsWith(t)) return e;
|
|
470
|
+
let r = `${n}${e.name.slice(t.length)}`;
|
|
471
|
+
return r === e.name ? e : e.name === `${t}_pkey` ? {
|
|
472
|
+
...e,
|
|
473
|
+
name: r,
|
|
474
|
+
table: n
|
|
475
|
+
} : i ? (a.push(L({
|
|
476
|
+
kind: "rename-index",
|
|
477
|
+
table: n,
|
|
478
|
+
from: e.name,
|
|
479
|
+
to: r
|
|
480
|
+
}, "safe", "auto-named index follows its renamed table — catalog-only, no rebuild")), {
|
|
481
|
+
...e,
|
|
482
|
+
name: r,
|
|
483
|
+
table: n
|
|
484
|
+
}) : e;
|
|
485
|
+
});
|
|
486
|
+
return {
|
|
487
|
+
live: {
|
|
488
|
+
...e,
|
|
489
|
+
indexes: o,
|
|
490
|
+
name: n
|
|
491
|
+
},
|
|
492
|
+
ops: a
|
|
493
|
+
};
|
|
494
|
+
}, le = (e) => {
|
|
495
|
+
let t = {
|
|
496
|
+
safe: 0,
|
|
497
|
+
needsDefault: 0,
|
|
498
|
+
needsBackfill: 0,
|
|
499
|
+
needsRenameAnnotation: 0,
|
|
500
|
+
lossy: 0,
|
|
501
|
+
onlineRequired: 0,
|
|
502
|
+
multiStep: 0,
|
|
503
|
+
blocked: 0
|
|
504
|
+
};
|
|
505
|
+
for (let n of e) switch (n.blocked && t.blocked++, n.classification) {
|
|
506
|
+
case "safe":
|
|
507
|
+
t.safe++;
|
|
508
|
+
break;
|
|
509
|
+
case "needs-default":
|
|
510
|
+
t.needsDefault++;
|
|
511
|
+
break;
|
|
512
|
+
case "needs-backfill":
|
|
513
|
+
t.needsBackfill++;
|
|
514
|
+
break;
|
|
515
|
+
case "needs-rename-annotation":
|
|
516
|
+
t.needsRenameAnnotation++;
|
|
517
|
+
break;
|
|
518
|
+
case "lossy":
|
|
519
|
+
t.lossy++;
|
|
520
|
+
break;
|
|
521
|
+
case "online-required":
|
|
522
|
+
t.onlineRequired++;
|
|
523
|
+
break;
|
|
524
|
+
case "multi-step": t.multiStep++;
|
|
525
|
+
}
|
|
526
|
+
return t;
|
|
527
|
+
}, X = (e, t) => {
|
|
528
|
+
let n = t.flatMap((e) => [e.table, e.targetTable]).flatMap((e) => {
|
|
529
|
+
let t = r(e);
|
|
530
|
+
return t === void 0 ? [] : [t.getTime()];
|
|
531
|
+
}), i = new Date(Math.max(...n)), a = new Date(Date.UTC(i.getUTCFullYear(), i.getUTCMonth(), i.getUTCDate() + 1)).toISOString().slice(0, 10);
|
|
532
|
+
return `${e.join("; ")}. Reclaim the snapshots first — \`voltro db gc-snapshots --before ${a}\` drops every snapshot older than that date, so run it with --dry-run to see the list — or, if a snapshot's data is still wanted, bring it back with \`voltro db restore-snapshot <plan-id>\` and declare it, so it converts in the same plan.`;
|
|
533
|
+
}, ue = (e, t, r, i) => {
|
|
534
|
+
let a = /* @__PURE__ */ new Map();
|
|
535
|
+
for (let n of e.tables) {
|
|
536
|
+
let e = t.get(n.name) ?? (r.has(n.name) ? t.get(r.get(n.name)) : void 0);
|
|
537
|
+
if (e === void 0) continue;
|
|
538
|
+
let o = new Set(n.columns.filter(S).map((e) => e.name));
|
|
539
|
+
x(e).filter((e) => o.has(e.column) && e.charset !== i).length > 0 && a.set(n.name, {
|
|
540
|
+
declared: n,
|
|
541
|
+
live: e
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (a.size === 0) return [];
|
|
545
|
+
let o = /* @__PURE__ */ new Map();
|
|
546
|
+
for (let e of t.values()) {
|
|
547
|
+
let t = /* @__PURE__ */ new Map();
|
|
548
|
+
for (let n of e.columns) t.has(n.name) || t.set(n.name, n);
|
|
549
|
+
o.set(e.name, t);
|
|
550
|
+
}
|
|
551
|
+
let s = /* @__PURE__ */ new Map(), c = (e, t) => {
|
|
552
|
+
let n = s.get(e);
|
|
553
|
+
n === void 0 ? s.set(e, [t]) : n.push(t);
|
|
554
|
+
};
|
|
555
|
+
for (let e of t.values()) for (let t of e.columns) {
|
|
556
|
+
if (t.references === void 0) continue;
|
|
557
|
+
let n = o.get(t.references.table)?.get(t.references.column), r = {
|
|
558
|
+
name: t.references.constraintName ?? `${e.name}_${t.name}_fkey`,
|
|
559
|
+
table: e.name,
|
|
560
|
+
column: t.name,
|
|
561
|
+
targetTable: t.references.table,
|
|
562
|
+
targetColumn: t.references.column,
|
|
563
|
+
...t.references.onDelete === void 0 ? {} : { onDelete: t.references.onDelete },
|
|
564
|
+
...t.references.onUpdate === void 0 ? {} : { onUpdate: t.references.onUpdate },
|
|
565
|
+
restore: "here",
|
|
566
|
+
sourceCharset: t.charset,
|
|
567
|
+
targetCharset: n?.charset
|
|
568
|
+
};
|
|
569
|
+
c(e.name, r), t.references.table !== e.name && c(t.references.table, r);
|
|
570
|
+
}
|
|
571
|
+
let l = [];
|
|
572
|
+
for (let [e, { declared: t, live: r }] of a) {
|
|
573
|
+
let o = t.columns.filter(S).map((e) => e.name), c = new Map(r.columns.map((e) => [e.name, e])), u = [...new Set(o.flatMap((e) => {
|
|
574
|
+
let t = c.get(e);
|
|
575
|
+
return t?.charset === void 0 ? [] : [t.charset];
|
|
576
|
+
}))].sort(), d = o.filter((e) => c.has(e)), f = s.get(e) ?? [], p = [], m = [], h = f.map((t) => {
|
|
577
|
+
let r = t.table === e ? t.targetTable : t.table, o = r === e || a.has(r), s = t.table === e ? t.targetCharset : t.sourceCharset;
|
|
578
|
+
if (!o && s !== void 0 && s !== i) {
|
|
579
|
+
let e = `${t.name} (${t.table}.${t.column} → ${t.targetTable}.${t.targetColumn})`;
|
|
580
|
+
n(r) ? m.push(`${e}: "${r}" is a soft-drop snapshot, stored ${s}`) : p.push(`${e}: "${r}" stays ${s} — it is not a declared table, so the plan does not convert it`);
|
|
581
|
+
}
|
|
582
|
+
let { sourceCharset: c, targetCharset: l, ...u } = t, d = o && r !== e && r.localeCompare(e) > 0 ? "peer" : "here";
|
|
583
|
+
return {
|
|
584
|
+
...u,
|
|
585
|
+
restore: d
|
|
586
|
+
};
|
|
587
|
+
}), g = i === "utf8mb4", _ = `table rewrite: ${d.length} string column${d.length === 1 ? "" : "s"} ${u.join("/")} → ${i}` + (h.length > 0 ? `; ${h.length} foreign key${h.length === 1 ? "" : "s"} dropped and re-created around it` : ""), v = {
|
|
588
|
+
kind: "convert-charset",
|
|
589
|
+
table: e,
|
|
590
|
+
charset: i,
|
|
591
|
+
columns: d,
|
|
592
|
+
from: u,
|
|
593
|
+
foreignKeys: h
|
|
594
|
+
};
|
|
595
|
+
if (p.length > 0 || m.length > 0) {
|
|
596
|
+
let t = [...p.length > 0 ? [`${p.join("; ")}. Declare that table so it converts in the same plan, convert it by hand first, or leave the store charset undeclared.`] : [], ...m.length > 0 ? [X(m, f)] : []];
|
|
597
|
+
l.push(L(v, "lossy", _, { fix: `converting "${e}" to ${i} would break a foreign key the plan cannot carry: ${t.join(" ")}` }));
|
|
598
|
+
} else l.push(g ? L(v, "safe", _) : L(v, "lossy", _, { fix: `${e}: narrowing to ${i} can refuse or truncate 4-byte characters the columns already hold. Declare charset: 'utf8mb4' instead, or — after checking the data — allow it with the destructive opt-in (VOLTRO_DESTRUCTIVE_OK=1).` }));
|
|
599
|
+
}
|
|
600
|
+
return l;
|
|
601
|
+
}, Z = (e) => {
|
|
602
|
+
let r = C(e.declared, e.dialect, { charset: e.charset }), i = ee(e.declared, r), s = new Map(e.live.tables.map((e) => [e.name, e])), c = e.onlineAfter ?? H, l = [], u = se(r, s, i);
|
|
603
|
+
l.push(...u.ops);
|
|
604
|
+
for (let e of r.tables) if (!u.renamedInto.has(e.name) && !s.has(e.name)) {
|
|
605
|
+
l.push(B(e.name, e.columns, e.indexes, e.primaryKey, u.refusals.get(e.name)));
|
|
606
|
+
for (let t of e.columns) {
|
|
607
|
+
let n = F(e.name, t);
|
|
608
|
+
n && l.push(n);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
let d = new Set(e.ignoreTables ?? []), f = [];
|
|
612
|
+
for (let r of e.live.tables) i.has(r.name) || (u.claimedOldNames.has(r.name) || t(r.name) || n(r.name) || d.has(r.name) ? f.push(r.name) : l.push(V(r.name)));
|
|
613
|
+
for (let t of r.tables) {
|
|
614
|
+
let r = u.renamedInto.get(t.name), a = s.get(t.name) ?? (r === void 0 ? void 0 : s.get(r));
|
|
615
|
+
if (!a) continue;
|
|
616
|
+
let o = ce(a, r, t.name, e.dialect);
|
|
617
|
+
l.push(...o.ops);
|
|
618
|
+
let d = o.live, f = i.get(t.name), p = te(t, d, e.dialect), m = ae(f.table, f.columnDefs, p), h = new Map(m.ops.flatMap((e) => e.op.kind === "rename-column" ? [[e.op.from, e.op.to]] : [])), g = ne(t, d, e.dialect, h);
|
|
619
|
+
l.push(...m.ops);
|
|
620
|
+
let _ = m.ops.length >= 8 ? N(t.columns) : void 0, v = m.ops.length >= 8 ? N(d.columns) : void 0;
|
|
621
|
+
for (let { op: n } of m.ops) {
|
|
622
|
+
if (n.kind !== "rename-column") continue;
|
|
623
|
+
let r = _ === void 0 ? t.columns.find((e) => e.name === n.to) : _.get(n.to), i = v === void 0 ? d.columns.find((e) => e.name === n.from) : v.get(n.from);
|
|
624
|
+
r && i && l.push(...G(t.name, r, {
|
|
625
|
+
...i,
|
|
626
|
+
name: n.to
|
|
627
|
+
}, e.dialect, d.rowCount, c));
|
|
628
|
+
}
|
|
629
|
+
for (let e of p.removed) {
|
|
630
|
+
if (m.consumedRemovedNames.has(e.name) || n(e.name)) continue;
|
|
631
|
+
let r = f.columnDefs[e.name], i = z(t.name, e, r);
|
|
632
|
+
e.references !== void 0 && l.push({
|
|
633
|
+
...L({
|
|
634
|
+
kind: "drop-foreign-key",
|
|
635
|
+
table: t.name,
|
|
636
|
+
column: e.name,
|
|
637
|
+
...e.references.constraintName === void 0 ? {} : { name: e.references.constraintName }
|
|
638
|
+
}, i.blocked === void 0 ? "safe" : i.classification, `drop FK on ${t.name}.${e.name} (→ ${e.references.table}) before dropping the column`),
|
|
639
|
+
...i.blocked === void 0 ? {} : { blocked: i.blocked }
|
|
640
|
+
}), l.push(i);
|
|
641
|
+
}
|
|
642
|
+
for (let n of p.added) {
|
|
643
|
+
if (m.consumedAddedNames.has(n.name)) continue;
|
|
644
|
+
let r = f.columnDefs[n.name];
|
|
645
|
+
l.push(R(t.name, n, r, e.dialect));
|
|
646
|
+
let i = F(t.name, n);
|
|
647
|
+
i && l.push(i), l.push(...K(t.name, n));
|
|
648
|
+
}
|
|
649
|
+
let y = oe(t.name, g, h, new Set(d.indexes.map((e) => e.name)), e.dialect);
|
|
650
|
+
l.push(...y.ops);
|
|
651
|
+
let b = p.added.some((e) => D(e) && !m.consumedAddedNames.has(e.name));
|
|
652
|
+
for (let e of g.added) y.consumedAddedNames.has(e.name) || b && e.name === `${t.name}_pkey` || l.push(re(t.name, e, d.rowCount, c));
|
|
653
|
+
for (let e of g.removed) y.consumedRemovedNames.has(e.name) || l.push(ie(t.name, e));
|
|
654
|
+
for (let { declared: n, live: r } of p.changed) l.push(...G(t.name, n, r, e.dialect, d.rowCount, c));
|
|
655
|
+
}
|
|
656
|
+
e.charset !== void 0 && l.push(...ue(r, s, u.renamedInto, e.charset));
|
|
657
|
+
let p = new Set(l.flatMap((e) => e.op.kind === "create-table" ? [e.op.table] : [])), m = new Map(r.tables.map((e) => [e.name, e])), h = (e) => {
|
|
658
|
+
let t = m.get(e);
|
|
659
|
+
return t ? t.columns.flatMap((e) => e.references && p.has(e.references.table) ? [{
|
|
660
|
+
column: e.name,
|
|
661
|
+
target: e.references.table
|
|
662
|
+
}] : []) : [];
|
|
663
|
+
}, g = a([...p], h), _ = [];
|
|
664
|
+
for (let e of r.tables) if (p.has(e.name)) for (let t of e.columns) t.references && g.has(o(e.name, t.name)) && _.push(L({
|
|
665
|
+
kind: "add-foreign-key",
|
|
666
|
+
table: e.name,
|
|
667
|
+
column: t.name,
|
|
668
|
+
targetTable: t.references.table,
|
|
669
|
+
targetColumn: t.references.column,
|
|
670
|
+
...t.references.onDelete ? { onDelete: t.references.onDelete } : {},
|
|
671
|
+
...t.references.onUpdate ? { onUpdate: t.references.onUpdate } : {}
|
|
672
|
+
}, "safe", `deferred FK ${e.name}.${t.name} → ${t.references.table} — breaks a create-time table cycle`));
|
|
673
|
+
if (g.size > 0) {
|
|
674
|
+
let e = (e) => e.references ? Object.fromEntries(Object.entries(e).filter(([e]) => e !== "references")) : e;
|
|
675
|
+
for (let t = 0; t < l.length; t += 1) {
|
|
676
|
+
let n = l[t];
|
|
677
|
+
if (n.op.kind !== "create-table") continue;
|
|
678
|
+
let r = n.op;
|
|
679
|
+
if (!r.columns.some((e) => g.has(o(r.table, e.name)))) continue;
|
|
680
|
+
let i = r.columns.map((t) => g.has(o(r.table, t.name)) ? e(t) : t);
|
|
681
|
+
l[t] = {
|
|
682
|
+
...n,
|
|
683
|
+
op: {
|
|
684
|
+
...r,
|
|
685
|
+
columns: i
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
l.push(..._);
|
|
690
|
+
}
|
|
691
|
+
let v = new Map(r.tables.map((e) => [e.name, e])), y = /* @__PURE__ */ new Map(), x = /* @__PURE__ */ new Set(), S = (e) => {
|
|
692
|
+
let t = y.get(e);
|
|
693
|
+
if (t !== void 0) return t;
|
|
694
|
+
if (x.has(e)) return 0;
|
|
695
|
+
let n = v.get(e);
|
|
696
|
+
if (!n) return 0;
|
|
697
|
+
x.add(e);
|
|
698
|
+
try {
|
|
699
|
+
let t = 0;
|
|
700
|
+
for (let r of n.columns) {
|
|
701
|
+
let n = r.references?.table;
|
|
702
|
+
n && n !== e && !g.has(o(e, r.name)) && (t = Math.max(t, S(n) + 1));
|
|
703
|
+
}
|
|
704
|
+
return y.set(e, t), t;
|
|
705
|
+
} finally {
|
|
706
|
+
x.delete(e);
|
|
707
|
+
}
|
|
708
|
+
}, w = (e) => e === "rename-table" ? -1 : e === "create-table" ? 0 : e === "drop-table" ? 2 : 1, T = (e) => {
|
|
709
|
+
let t = "table" in e ? e.table : "";
|
|
710
|
+
return e.kind === "create-table" ? S(t) : e.kind === "drop-table" ? -S(t) : 0;
|
|
711
|
+
}, E = {
|
|
712
|
+
"add-column": 0,
|
|
713
|
+
"rename-column": 1,
|
|
714
|
+
"rename-index": 1,
|
|
715
|
+
"alter-column-nullability": 2,
|
|
716
|
+
"alter-column-type": 2,
|
|
717
|
+
"alter-column-default": 2,
|
|
718
|
+
"drop-index": 3,
|
|
719
|
+
"drop-check": 3,
|
|
720
|
+
"drop-unique": 3,
|
|
721
|
+
"drop-unique-composite": 3,
|
|
722
|
+
"drop-foreign-key": 3,
|
|
723
|
+
"drop-column": 4,
|
|
724
|
+
"convert-charset": 5,
|
|
725
|
+
"add-index": 6,
|
|
726
|
+
"add-unique": 6,
|
|
727
|
+
"add-unique-composite": 6,
|
|
728
|
+
"add-foreign-key": 6,
|
|
729
|
+
"add-check": 6,
|
|
730
|
+
"reconcile-json-check": 6
|
|
731
|
+
}, O = (e) => E[e] ?? 9, k = (e) => e.kind === "drop-unique-composite" && e.name === `${e.table}_pkey`, A = (e) => k(e) ? -1 : O(e.kind), j = new Set(l.flatMap((e) => "table" in e.op && O(e.op.kind) < O("convert-charset") ? [e.op.table] : [])), M = (e) => e.kind === "convert-charset" && !j.has(e.table) ? -.5 : w(e.kind), P = [...l].sort((e, t) => {
|
|
732
|
+
let n = M(e.op), r = M(t.op);
|
|
733
|
+
if (n !== r) return n - r;
|
|
734
|
+
let i = T(e.op), a = T(t.op);
|
|
735
|
+
if (i !== a) return i - a;
|
|
736
|
+
let o = "table" in e.op ? e.op.table : "", s = "table" in t.op ? t.op.table : "";
|
|
737
|
+
if (o !== s) return o.localeCompare(s);
|
|
738
|
+
let c = A(e.op), l = A(t.op);
|
|
739
|
+
return c === l ? e.op.kind.localeCompare(t.op.kind) : c - l;
|
|
740
|
+
}), I = new Set(l.flatMap((e) => e.op.kind === "convert-charset" ? [e.op.table] : [])), U = new Set(l.flatMap((e) => e.op.kind === "create-table" ? [e.op.table] : [])), W = /* @__PURE__ */ new Set(), q = [], J = [], Y = (e) => !U.has(e) || W.has(e), X = (e) => {
|
|
741
|
+
let t = e.op;
|
|
742
|
+
return t.kind === "create-table" ? t.columns.every((e) => {
|
|
743
|
+
let n = e.references?.table;
|
|
744
|
+
return n === void 0 || n === t.table || g.has(o(t.table, e.name)) || !I.has(n) && Y(n);
|
|
745
|
+
}) : "table" in t && !Y(t.table) ? !1 : t.kind !== "add-foreign-key" || !I.has(t.table) && !I.has(t.targetTable) && Y(t.targetTable);
|
|
746
|
+
}, Z = (e) => {
|
|
747
|
+
J.push(e), e.op.kind === "convert-charset" && I.delete(e.op.table), e.op.kind === "create-table" && W.add(e.op.table);
|
|
748
|
+
};
|
|
749
|
+
for (let e of P) for (X(e) ? Z(e) : q.push(e); q.length > 0;) {
|
|
750
|
+
let e = q.findIndex(X);
|
|
751
|
+
if (e < 0) break;
|
|
752
|
+
Z(q.splice(e, 1)[0]);
|
|
753
|
+
}
|
|
754
|
+
J.push(...q);
|
|
755
|
+
let Q = /* @__PURE__ */ new Set();
|
|
756
|
+
for (let e of J) "table" in e.op && Q.add(e.op.table);
|
|
757
|
+
let $ = [];
|
|
758
|
+
for (let e of r.tables) {
|
|
759
|
+
if (Q.has(e.name) || u.renamedInto.has(e.name)) continue;
|
|
760
|
+
let t = s.get(e.name);
|
|
761
|
+
t !== void 0 && b({ tables: [t] }) !== b({ tables: [e] }) && $.push(e.name);
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
operations: J,
|
|
765
|
+
fromFingerprint: b(e.live),
|
|
766
|
+
toFingerprint: b(r),
|
|
767
|
+
summary: le(J),
|
|
768
|
+
fingerprintDivergence: {
|
|
769
|
+
outsideDiff: f,
|
|
770
|
+
diffEquivalent: $
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
};
|
|
774
|
+
//#endregion
|
|
775
|
+
export { Z as planMigrations };
|