@vibeorm/migrate 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adoption.d.ts.map +1 -1
- package/dist/baseline-record.d.ts +14 -0
- package/dist/baseline-record.d.ts.map +1 -0
- package/dist/baseline.d.ts +31 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/compare-reference-rls.d.ts +10 -0
- package/dist/compare-reference-rls.d.ts.map +1 -0
- package/dist/compare-sqlite-primary-key.d.ts +6 -0
- package/dist/compare-sqlite-primary-key.d.ts.map +1 -0
- package/dist/ddl/sqlite.d.ts.map +1 -1
- package/dist/desired-state.d.ts.map +1 -1
- package/dist/fs.js +735 -452
- package/dist/fs.js.map +19 -16
- package/dist/index-predicates.d.ts +35 -0
- package/dist/index-predicates.d.ts.map +1 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6430 -5110
- package/dist/index.js.map +48 -34
- package/dist/inspect-objects.d.ts +8 -1
- package/dist/inspect-objects.d.ts.map +1 -1
- package/dist/introspect/postgres.d.ts.map +1 -1
- package/dist/module-history.d.ts +6 -0
- package/dist/module-history.d.ts.map +1 -1
- package/dist/module-ownership.d.ts +8 -0
- package/dist/module-ownership.d.ts.map +1 -1
- package/dist/module-pending.d.ts +2 -0
- package/dist/module-pending.d.ts.map +1 -1
- package/dist/mysql-catalog-visibility.d.ts +5 -0
- package/dist/mysql-catalog-visibility.d.ts.map +1 -0
- package/dist/normalize.d.ts.map +1 -1
- package/dist/object-ownership.d.ts.map +1 -1
- package/dist/planner.d.ts +5 -0
- package/dist/planner.d.ts.map +1 -1
- package/dist/postgres-type-identity.d.ts +15 -0
- package/dist/postgres-type-identity.d.ts.map +1 -0
- package/dist/postgres-types.d.ts +21 -0
- package/dist/postgres-types.d.ts.map +1 -1
- package/dist/push.d.ts.map +1 -1
- package/dist/reference-state.d.ts.map +1 -1
- package/dist/replay-objects.d.ts +10 -0
- package/dist/replay-objects.d.ts.map +1 -0
- package/dist/replay-plan.d.ts +28 -0
- package/dist/replay-plan.d.ts.map +1 -0
- package/dist/replay-reference.d.ts +18 -0
- package/dist/replay-reference.d.ts.map +1 -0
- package/dist/runner.d.ts +6 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/shadow-dedicated.d.ts +12 -0
- package/dist/shadow-dedicated.d.ts.map +1 -0
- package/dist/shadow-guard.d.ts +13 -0
- package/dist/shadow-guard.d.ts.map +1 -0
- package/dist/shadow-types.d.ts +52 -0
- package/dist/shadow-types.d.ts.map +1 -0
- package/dist/shadow.d.ts +17 -0
- package/dist/shadow.d.ts.map +1 -0
- package/dist/verify-history.d.ts +35 -0
- package/dist/verify-history.d.ts.map +1 -0
- package/package.json +3 -3
package/dist/fs.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/migrate/src/mysql-catalog-visibility.ts
|
|
3
|
+
function hasMysqlGlobalCatalogVisibility(params) {
|
|
4
|
+
let visible = false;
|
|
5
|
+
for (const row of params.rows) {
|
|
6
|
+
const values = Object.values(row);
|
|
7
|
+
if (values.length !== 1 || typeof values[0] !== "string" || !values[0].startsWith("GRANT "))
|
|
8
|
+
return false;
|
|
9
|
+
const grant = /^GRANT ([A-Z_ ,]+) ON \*\.\* TO /.exec(values[0]);
|
|
10
|
+
const privileges = grant?.[1]?.split(", ") ?? [];
|
|
11
|
+
if (privileges.some((privilege) => privilege === "SHOW VIEW" || privilege === "SELECT" || privilege === "ALL PRIVILEGES"))
|
|
12
|
+
visible = true;
|
|
13
|
+
}
|
|
14
|
+
return visible;
|
|
15
|
+
}
|
|
16
|
+
|
|
2
17
|
// packages/migrate/src/module-ownership.ts
|
|
3
|
-
import { VibeError as
|
|
18
|
+
import { VibeError as VibeError17, sqlObjectKey as sqlObjectKey6 } from "@vibeorm/schema";
|
|
19
|
+
import { getSqlDialect as getSqlDialect3, renderModuleDependencyQuery } from "@vibeorm/sql";
|
|
4
20
|
|
|
5
21
|
// packages/migrate/src/desired-state.ts
|
|
6
22
|
import { VibeError, assertValidSchemaIR, isSqlObjectDefinition, isSqlObjectIdentity, providerToDialect, sqlObjectKey } from "@vibeorm/schema";
|
|
@@ -38,7 +54,7 @@ function keysOnly(value, allowed) {
|
|
|
38
54
|
function modelShape(value) {
|
|
39
55
|
if (!record(value) || typeof value.name !== "string" || !Array.isArray(value.fields) || !Array.isArray(value.relations) || !Array.isArray(value.indexes))
|
|
40
56
|
return false;
|
|
41
|
-
return value.fields.every((field) => record(field) && typeof field.name === "string" && (typeof field.type === "string" || record(field.type) && typeof field.type.enum === "string") && ["isId", "isUnique", "isOptional", "isList"].every((key) => typeof field[key] === "boolean")) && value.relations.every(record) && value.indexes.every((index) => record(index) && Array.isArray(index.fields) && index.fields.every(record)) && (value.checks === undefined || Array.isArray(value.checks) && value.checks.every((check) => record(check) && typeof check.name === "string" && typeof check.expression === "string"));
|
|
57
|
+
return value.fields.every((field) => record(field) && typeof field.name === "string" && (field.nativeTypeIdentity === undefined || record(field.nativeTypeIdentity) && keysOnly(field.nativeTypeIdentity, ["source", "namespace"]) && field.isUnsupported === true && typeof field.nativeTypeIdentity.source === "string" && field.nativeTypeIdentity.source === field.nativeType && typeof field.nativeTypeIdentity.namespace === "string" && field.nativeTypeIdentity.namespace.length > 0) && (typeof field.type === "string" || record(field.type) && typeof field.type.enum === "string") && ["isId", "isUnique", "isOptional", "isList"].every((key) => typeof field[key] === "boolean")) && value.relations.every(record) && value.indexes.every((index) => record(index) && Array.isArray(index.fields) && index.fields.every(record)) && (value.checks === undefined || Array.isArray(value.checks) && value.checks.every((check) => record(check) && typeof check.name === "string" && typeof check.expression === "string"));
|
|
42
58
|
}
|
|
43
59
|
function state(value) {
|
|
44
60
|
if (!record(value) || !keysOnly(value, ["schema", "definitions", "normalization"]) || !record(value.schema) || !Array.isArray(value.definitions) || !value.definitions.every(isSqlObjectDefinition))
|
|
@@ -100,10 +116,10 @@ function parseMigrationMetadata(params) {
|
|
|
100
116
|
}
|
|
101
117
|
|
|
102
118
|
// packages/migrate/src/object-ownership.ts
|
|
103
|
-
import { VibeError as VibeError6,
|
|
119
|
+
import { VibeError as VibeError6, defaultPrimaryKeyName, enumStorageOf as enumStorageOf2, implicitJoinTableObjectNames, postgresGeneratedObjectName, sqlObjectKey as sqlObjectKey2 } from "@vibeorm/schema";
|
|
104
120
|
|
|
105
121
|
// packages/migrate/src/normalize.ts
|
|
106
|
-
import { VibeError as VibeError5, enumStorageOf, isEnumType as isEnumType5, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
122
|
+
import { VibeError as VibeError5, defaultForeignKeyName, defaultIndexName, enumStorageOf, isEnumType as isEnumType5, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
107
123
|
|
|
108
124
|
// packages/migrate/src/relations.ts
|
|
109
125
|
import { implicitJoinTable, isImplicitManyToManyRelation } from "@vibeorm/schema";
|
|
@@ -138,7 +154,7 @@ function detectImplicitM2MPairs(params) {
|
|
|
138
154
|
|
|
139
155
|
// packages/migrate/src/postgres-types.ts
|
|
140
156
|
import { VibeError as VibeError2, isEnumType, scalarListDefault, serializeJsonDefault } from "@vibeorm/schema";
|
|
141
|
-
import { POSTGRES_CODECS, postgresDialect, toPgArrayLiteral } from "@vibeorm/sql";
|
|
157
|
+
import { POSTGRES_CODECS, postgresDialect, toPgArrayLiteral, parsePostgresTypeName, renderPostgresTypeName } from "@vibeorm/sql";
|
|
142
158
|
var SCALAR_TO_PG = {
|
|
143
159
|
String: "text",
|
|
144
160
|
Boolean: "boolean",
|
|
@@ -153,6 +169,49 @@ var SCALAR_TO_PG = {
|
|
|
153
169
|
var CANONICAL_EQUIVALENTS = {
|
|
154
170
|
DateTime: ["timestamptz(3)", "timestamptz(6)"]
|
|
155
171
|
};
|
|
172
|
+
var CANONICAL_UDT = {
|
|
173
|
+
text: "String",
|
|
174
|
+
bool: "Boolean",
|
|
175
|
+
int4: "Int",
|
|
176
|
+
int8: "BigInt",
|
|
177
|
+
float8: "Float",
|
|
178
|
+
numeric: "Decimal",
|
|
179
|
+
timestamptz: "DateTime",
|
|
180
|
+
jsonb: "Json",
|
|
181
|
+
bytea: "Bytes"
|
|
182
|
+
};
|
|
183
|
+
var NON_CANONICAL_UDT = {
|
|
184
|
+
varchar: "String",
|
|
185
|
+
bpchar: "String",
|
|
186
|
+
uuid: "String",
|
|
187
|
+
bit: "String",
|
|
188
|
+
varbit: "String",
|
|
189
|
+
xml: "String",
|
|
190
|
+
inet: "String",
|
|
191
|
+
citext: "String",
|
|
192
|
+
int2: "Int",
|
|
193
|
+
oid: "Int",
|
|
194
|
+
float4: "Float",
|
|
195
|
+
money: "Decimal",
|
|
196
|
+
timestamp: "DateTime",
|
|
197
|
+
date: "DateTime",
|
|
198
|
+
time: "DateTime",
|
|
199
|
+
timetz: "DateTime",
|
|
200
|
+
json: "Json"
|
|
201
|
+
};
|
|
202
|
+
var SPELLING_TO_UDT = {
|
|
203
|
+
integer: "int4",
|
|
204
|
+
bigint: "int8",
|
|
205
|
+
smallint: "int2",
|
|
206
|
+
boolean: "bool",
|
|
207
|
+
doubleprecision: "float8",
|
|
208
|
+
real: "float4",
|
|
209
|
+
char: "bpchar"
|
|
210
|
+
};
|
|
211
|
+
function scalarForBuiltinType(params) {
|
|
212
|
+
const udt = SPELLING_TO_UDT[params.name] ?? params.name;
|
|
213
|
+
return CANONICAL_UDT[udt] ?? NON_CANONICAL_UDT[udt];
|
|
214
|
+
}
|
|
156
215
|
var NATIVE_ALIASES = {
|
|
157
216
|
charactervarying: "varchar",
|
|
158
217
|
character: "char",
|
|
@@ -172,7 +231,11 @@ var NATIVE_ALIASES = {
|
|
|
172
231
|
timestampwithtimezone: "timestamptz",
|
|
173
232
|
timestampwithouttimezone: "timestamp",
|
|
174
233
|
timewithtimezone: "timetz",
|
|
175
|
-
timewithouttimezone: "time"
|
|
234
|
+
timewithouttimezone: "time",
|
|
235
|
+
bitvarying: "varbit"
|
|
236
|
+
};
|
|
237
|
+
var IMPLICIT_MODIFIERS = {
|
|
238
|
+
bit: "(1)"
|
|
176
239
|
};
|
|
177
240
|
function normalizeNativeType(params) {
|
|
178
241
|
const compact = params.raw.toLowerCase().replace(/\s+/g, "");
|
|
@@ -180,13 +243,33 @@ function normalizeNativeType(params) {
|
|
|
180
243
|
if (match === null)
|
|
181
244
|
return compact;
|
|
182
245
|
const base = NATIVE_ALIASES[match[1]] ?? match[1];
|
|
183
|
-
return `${base}${match[2] ?? ""}${match[3] ?? ""}`;
|
|
246
|
+
return `${base}${match[2] ?? IMPLICIT_MODIFIERS[base] ?? ""}${match[3] ?? ""}`;
|
|
247
|
+
}
|
|
248
|
+
function parseUnsupportedPostgresType(params) {
|
|
249
|
+
const temporal = /^\s*(time(?:stamp)?)\s*(\(\s*\d+\s*\))?\s+(with|without)\s+time\s+zone\s*((?:\[\s*\]\s*)*)$/i.exec(params.raw);
|
|
250
|
+
if (temporal !== null)
|
|
251
|
+
return parsePostgresTypeName({ raw: `${temporal[1].toLowerCase()}${temporal[3].toLowerCase() === "with" ? "tz" : ""}${temporal[2] ?? ""}${temporal[4] ?? ""}` });
|
|
252
|
+
const parsed = parsePostgresTypeName(params);
|
|
253
|
+
if (parsed !== undefined || /["']/.test(params.raw))
|
|
254
|
+
return parsed;
|
|
255
|
+
const base = params.raw.toLowerCase().replace(/\s+/g, "").replace(/\(.*\)|\[\]/g, "");
|
|
256
|
+
return NATIVE_ALIASES[base] === undefined ? undefined : parsePostgresTypeName({ raw: normalizeNativeType(params) });
|
|
184
257
|
}
|
|
185
258
|
function columnTypeKey(params) {
|
|
186
259
|
const { field } = params;
|
|
187
260
|
const listSuffix = field.isList ? "[]" : "";
|
|
188
261
|
if (field.isUnsupported === true) {
|
|
189
|
-
|
|
262
|
+
const parsed = parseUnsupportedPostgresType({ raw: field.nativeType ?? "" });
|
|
263
|
+
if (parsed === undefined)
|
|
264
|
+
return `u:${field.nativeType ?? ""}`;
|
|
265
|
+
const evidence = field.nativeTypeIdentity;
|
|
266
|
+
const namespace = evidence !== undefined && evidence.source === field.nativeType ? evidence.namespace : parsed.namespace;
|
|
267
|
+
const scalar = namespace === undefined || namespace === "pg_catalog" ? scalarForBuiltinType({ name: parsed.name }) : undefined;
|
|
268
|
+
if (scalar !== undefined) {
|
|
269
|
+
const { isUnsupported: _unsupported, nativeTypeIdentity: _identity, ...rest } = field;
|
|
270
|
+
return columnTypeKey({ field: { ...rest, type: scalar, nativeType: `${parsed.name}${parsed.modifier ?? ""}`, isList: parsed.array || field.isList } });
|
|
271
|
+
}
|
|
272
|
+
return `u:${JSON.stringify([namespace ?? null, parsed.name, parsed.modifier, parsed.array || field.isList])}`;
|
|
190
273
|
}
|
|
191
274
|
if (isEnumType(field.type)) {
|
|
192
275
|
return `e:${field.type.enum}${listSuffix}`;
|
|
@@ -218,6 +301,10 @@ function columnTypeSql(params) {
|
|
|
218
301
|
meta: { field: field.name }
|
|
219
302
|
});
|
|
220
303
|
}
|
|
304
|
+
const parsed = parseUnsupportedPostgresType({ raw: field.nativeType });
|
|
305
|
+
const evidence = field.nativeTypeIdentity;
|
|
306
|
+
if (parsed !== undefined && evidence?.source === field.nativeType)
|
|
307
|
+
return renderPostgresTypeName({ dialect: postgresDialect, type: { ...parsed, namespace: evidence.namespace, array: parsed.array || field.isList } });
|
|
221
308
|
return field.nativeType;
|
|
222
309
|
}
|
|
223
310
|
const listSuffix = field.isList ? "[]" : "";
|
|
@@ -270,8 +357,12 @@ function defaultFingerprint(params) {
|
|
|
270
357
|
}
|
|
271
358
|
case "enumValue":
|
|
272
359
|
return `enum:${def.value}`;
|
|
273
|
-
case "dbgenerated":
|
|
274
|
-
|
|
360
|
+
case "dbgenerated": {
|
|
361
|
+
const sql = def.sql.trim();
|
|
362
|
+
if (/^now\(\)$/i.test(sql) || /^current_timestamp(\(\d\))?$/i.test(sql))
|
|
363
|
+
return "now";
|
|
364
|
+
return `db:${sql}`;
|
|
365
|
+
}
|
|
275
366
|
}
|
|
276
367
|
}
|
|
277
368
|
function defaultSql(params) {
|
|
@@ -762,8 +853,8 @@ function normalizeModel(params) {
|
|
|
762
853
|
});
|
|
763
854
|
}
|
|
764
855
|
const pk = normalizePk({ model });
|
|
765
|
-
const indexes = normalizeIndexes({ model, table });
|
|
766
|
-
const fks = normalizeFks({ model, table, schema });
|
|
856
|
+
const indexes = normalizeIndexes({ model, table, dialect });
|
|
857
|
+
const fks = normalizeFks({ model, table, schema, dialect });
|
|
767
858
|
const checks = new Map;
|
|
768
859
|
for (const check of model.checks ?? [])
|
|
769
860
|
checks.set(check.name, check.expression);
|
|
@@ -777,7 +868,7 @@ function normalizePk(params) {
|
|
|
777
868
|
return model.fields.filter((field) => field.isId).map((field) => field.dbName ?? field.name);
|
|
778
869
|
}
|
|
779
870
|
function normalizeIndexes(params) {
|
|
780
|
-
const { model, table } = params;
|
|
871
|
+
const { model, table, dialect } = params;
|
|
781
872
|
const result = new Map;
|
|
782
873
|
const add = (index) => {
|
|
783
874
|
const unique = index.kind === "unique";
|
|
@@ -796,7 +887,7 @@ function normalizeIndexes(params) {
|
|
|
796
887
|
const key = indexComparisonKey({ unique, kind, columns, where, nullsNotDistinct });
|
|
797
888
|
if (result.has(key))
|
|
798
889
|
return;
|
|
799
|
-
const name = index.name ??
|
|
890
|
+
const name = index.name ?? defaultIndexName({ table, columns: columns.map((column) => column.name), unique, dialect });
|
|
800
891
|
result.set(key, {
|
|
801
892
|
key,
|
|
802
893
|
name,
|
|
@@ -1002,7 +1093,7 @@ function normalizeFks(params) {
|
|
|
1002
1093
|
continue;
|
|
1003
1094
|
result.set(key, {
|
|
1004
1095
|
key,
|
|
1005
|
-
constraintName: relation.constraintName ??
|
|
1096
|
+
constraintName: relation.constraintName ?? defaultForeignKeyName({ table, columns, dialect: params.dialect }),
|
|
1006
1097
|
columns,
|
|
1007
1098
|
refTable,
|
|
1008
1099
|
refColumns,
|
|
@@ -1145,7 +1236,7 @@ function removalIdentity(params) {
|
|
|
1145
1236
|
case "dropForeignKey":
|
|
1146
1237
|
return { kind: "constraint", constraintType: "foreignKey", table, name: step.constraintName };
|
|
1147
1238
|
case "dropPrimaryKey":
|
|
1148
|
-
return { kind: "constraint", constraintType: "primaryKey", table, name: step.constraintName ??
|
|
1239
|
+
return { kind: "constraint", constraintType: "primaryKey", table, name: step.constraintName ?? defaultPrimaryKeyName({ table: step.table, dialect: "postgres" }) };
|
|
1149
1240
|
default:
|
|
1150
1241
|
return;
|
|
1151
1242
|
}
|
|
@@ -1164,7 +1255,7 @@ function schemaObjectIdentities(params) {
|
|
|
1164
1255
|
if (definition.defaultKey !== null)
|
|
1165
1256
|
result.push({ kind: "columnDefault", table, column });
|
|
1166
1257
|
if (params.dialect === "postgres" && definition.field.default?.kind === "autoincrement")
|
|
1167
|
-
result.push({ kind: "sequence", name: { namespace: params.namespace, name:
|
|
1258
|
+
result.push({ kind: "sequence", name: { namespace: params.namespace, name: postgresGeneratedObjectName({ name1: name, name2: column, label: "seq" }) } });
|
|
1168
1259
|
}
|
|
1169
1260
|
for (const exclusion of model.model.exclusions ?? [])
|
|
1170
1261
|
result.push({ kind: "constraint", constraintType: "exclusion", table, name: exclusion.name });
|
|
@@ -1178,15 +1269,15 @@ function schemaObjectIdentities(params) {
|
|
|
1178
1269
|
for (const fk of model.fks.values())
|
|
1179
1270
|
result.push({ kind: "constraint", constraintType: "foreignKey", table, name: fk.constraintName });
|
|
1180
1271
|
if (model.pk.length > 0)
|
|
1181
|
-
result.push({ kind: "constraint", constraintType: "primaryKey", table, name: model.model.primaryKeyConstraintName ?? (params.dialect === "mysql" ? "PRIMARY" :
|
|
1272
|
+
result.push({ kind: "constraint", constraintType: "primaryKey", table, name: model.model.primaryKeyConstraintName ?? (params.dialect === "mysql" ? "PRIMARY" : defaultPrimaryKeyName({ table: name, dialect: params.dialect })) });
|
|
1182
1273
|
}
|
|
1183
1274
|
for (const name of normalized.joinTables.keys()) {
|
|
1184
1275
|
const table = { namespace: params.namespace, name };
|
|
1185
1276
|
result.push({ kind: "table", name: table });
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1277
|
+
const derived = implicitJoinTableObjectNames({ table: name });
|
|
1278
|
+
result.push({ kind: "column", table, column: "A" }, { kind: "constraint", constraintType: "foreignKey", table, name: derived.foreignKeyA });
|
|
1279
|
+
result.push({ kind: "column", table, column: "B" }, { kind: "constraint", constraintType: "foreignKey", table, name: derived.foreignKeyB });
|
|
1280
|
+
result.push({ kind: "index", table, name: derived.uniqueIndex }, { kind: "index", table, name: derived.indexB });
|
|
1190
1281
|
}
|
|
1191
1282
|
for (const name of normalized.views.keys())
|
|
1192
1283
|
result.push({ kind: "view", name: { namespace: params.namespace, name } });
|
|
@@ -1196,7 +1287,7 @@ function assertRetainedMigrationObjects(params) {
|
|
|
1196
1287
|
const keys = new Set(params.to.map((object) => sqlObjectKey2({ identity: object.identity })));
|
|
1197
1288
|
const removed = params.from.filter((object) => !keys.has(sqlObjectKey2({ identity: object.identity })));
|
|
1198
1289
|
if (removed.length > 0)
|
|
1199
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Removing migration ownership requires a reviewed hand-authored migration and new desired state", meta: { reason: "migration-object-removal-review-required", objects: removed.map((object) => object.identity) } });
|
|
1290
|
+
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Removing migration ownership requires a reviewed hand-authored migration and new desired state", meta: { reason: "migration-object-removal-review-required", shadowReusable: true, objects: removed.map((object) => object.identity) } });
|
|
1200
1291
|
}
|
|
1201
1292
|
function orderedMigrationObjects(params) {
|
|
1202
1293
|
const byKey = new Map(params.objects.map((object) => [sqlObjectKey2({ identity: object.identity }), object]));
|
|
@@ -1227,10 +1318,10 @@ function assertNamedRemovals(params) {
|
|
|
1227
1318
|
const approved = new Set((params.approved ?? []).map((identity) => sqlObjectKey2({ identity })));
|
|
1228
1319
|
const missing = params.removals.filter((identity) => !approved.has(sqlObjectKey2({ identity })));
|
|
1229
1320
|
if (missing.length > 0)
|
|
1230
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Unmanaged removals require individually named approvals", meta: { reason: "unmanaged-removal-required", objects: missing } });
|
|
1321
|
+
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Unmanaged removals require individually named approvals", meta: { reason: "unmanaged-removal-required", shadowReusable: true, objects: missing } });
|
|
1231
1322
|
const integrity = params.removals.filter((identity) => identity.kind === "constraint" || identity.kind === "index");
|
|
1232
1323
|
if (integrity.length > 0 && params.acceptIntegrityLoss !== true)
|
|
1233
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Named constraint or index removals also require acceptIntegrityLoss", meta: { reason: "integrity-acknowledgement-required", objects: integrity } });
|
|
1324
|
+
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Named constraint or index removals also require acceptIntegrityLoss", meta: { reason: "integrity-acknowledgement-required", shadowReusable: true, objects: integrity } });
|
|
1234
1325
|
}
|
|
1235
1326
|
function assertRemovalAuthorization(params) {
|
|
1236
1327
|
const declared = params.previous === undefined ? [] : schemaObjectIdentities({ schema: params.previous.schema, dialect: params.dialect, namespace: params.namespace });
|
|
@@ -1255,14 +1346,14 @@ function assertRemovalAuthorization(params) {
|
|
|
1255
1346
|
}
|
|
1256
1347
|
}
|
|
1257
1348
|
if (unknown.length > 0)
|
|
1258
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Unknown object removals require individually named removeUnmanaged decisions", meta: { reason: "unmanaged-removal-required", objects: unknown } });
|
|
1349
|
+
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Unknown object removals require individually named removeUnmanaged decisions", meta: { reason: "unmanaged-removal-required", shadowReusable: true, objects: unknown } });
|
|
1259
1350
|
if (integrity.length > 0 && params.acceptIntegrityLoss !== true)
|
|
1260
|
-
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Constraint or index removal requires acceptIntegrityLoss; data-loss acknowledgement does not authorize it", meta: { reason: "integrity-acknowledgement-required", objects: integrity } });
|
|
1351
|
+
throw new VibeError6({ code: "VIBE_MIGRATION", message: "Constraint or index removal requires acceptIntegrityLoss; data-loss acknowledgement does not authorize it", meta: { reason: "integrity-acknowledgement-required", shadowReusable: true, objects: integrity } });
|
|
1261
1352
|
}
|
|
1262
1353
|
|
|
1263
1354
|
// packages/migrate/src/inventory.ts
|
|
1264
|
-
import { VibeError as
|
|
1265
|
-
import { getSqlDialect as getSqlDialect2, renderMigrationInventory, renderSqliteColumnInventory, renderMysqlObjectMembers as renderMysqlObjectMembers2, sqliteTableConstraints as sqliteTableConstraints3, renderSqliteForeignKeyInventory } from "@vibeorm/sql";
|
|
1355
|
+
import { VibeError as VibeError16, defaultForeignKeyName as defaultForeignKeyName2, defaultPrimaryKeyName as defaultPrimaryKeyName2, isSqlObjectIdentity as isSqlObjectIdentity2, sqlObjectKey as sqlObjectKey5 } from "@vibeorm/schema";
|
|
1356
|
+
import { getSqlDialect as getSqlDialect2, renderMigrationInventory, renderSqliteColumnInventory as renderSqliteColumnInventory2, renderMysqlObjectMembers as renderMysqlObjectMembers2, sqliteTableConstraints as sqliteTableConstraints3, renderSqliteForeignKeyInventory } from "@vibeorm/sql";
|
|
1266
1357
|
|
|
1267
1358
|
// packages/migrate/src/exclusions.ts
|
|
1268
1359
|
import { randomUUID } from "crypto";
|
|
@@ -1342,16 +1433,16 @@ async function verifyExclusions(params) {
|
|
|
1342
1433
|
|
|
1343
1434
|
// packages/migrate/src/inspect-objects.ts
|
|
1344
1435
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1345
|
-
import { VibeError as
|
|
1346
|
-
import { getSqlDialect, renderMigrationObjectQuery, renderCheckProbe, renderMigrationNamespace, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlCheckProbe, sqliteColumnClauses as sqliteColumnClauses2, renderMigrationVisibility, sqliteTableConstraints as sqliteTableConstraints2 } from "@vibeorm/sql";
|
|
1436
|
+
import { VibeError as VibeError15, sqlObjectKey as sqlObjectKey4, DIALECT_CAPABILITIES as DIALECT_CAPABILITIES2 } from "@vibeorm/schema";
|
|
1437
|
+
import { getSqlDialect, renderMigrationObjectBatch, renderMigrationObjectQuery, renderCheckProbe, renderMigrationNamespace, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlCheckProbe, sqliteColumnClauses as sqliteColumnClauses2, renderMigrationVisibility, sqliteTableConstraints as sqliteTableConstraints2, renderSqliteColumnInventory } from "@vibeorm/sql";
|
|
1347
1438
|
|
|
1348
1439
|
// packages/migrate/src/introspect/postgres.ts
|
|
1349
1440
|
import { renderExclusionCatalog as renderExclusionCatalog2 } from "@vibeorm/sql";
|
|
1350
1441
|
import { parsePgArrayLiteral } from "@vibeorm/sql";
|
|
1351
1442
|
import { scalarListDefault as scalarListDefault2 } from "@vibeorm/schema";
|
|
1352
1443
|
import { exactIntegerLiteral } from "@vibeorm/schema";
|
|
1353
|
-
import { postgresDialect as
|
|
1354
|
-
import { VibeError as
|
|
1444
|
+
import { postgresDialect as postgresDialect6, renderRlsReadinessQuery } from "@vibeorm/sql";
|
|
1445
|
+
import { VibeError as VibeError12 } from "@vibeorm/schema";
|
|
1355
1446
|
|
|
1356
1447
|
// packages/migrate/src/journal-table.ts
|
|
1357
1448
|
var MIGRATIONS_TABLE_NAME = "_vibe_migrations";
|
|
@@ -1543,12 +1634,58 @@ function lowerFirst(value) {
|
|
|
1543
1634
|
return value.charAt(0).toLowerCase() + value.slice(1);
|
|
1544
1635
|
}
|
|
1545
1636
|
|
|
1637
|
+
// packages/migrate/src/postgres-type-identity.ts
|
|
1638
|
+
import { VibeError as VibeError9 } from "@vibeorm/schema";
|
|
1639
|
+
import { postgresDialect as postgresDialect3, renderPostgresTypeIdentity } from "@vibeorm/sql";
|
|
1640
|
+
async function resolvePostgresTypeIdentities(params) {
|
|
1641
|
+
const unavailable = [];
|
|
1642
|
+
const models = [];
|
|
1643
|
+
for (const model of params.schema.models) {
|
|
1644
|
+
const table = { namespace: "public", name: model.dbName ?? model.name };
|
|
1645
|
+
const previous = params.previous?.models.find((entry) => (entry.dbName ?? entry.name) === table.name);
|
|
1646
|
+
const fields = [];
|
|
1647
|
+
for (const field of model.fields) {
|
|
1648
|
+
if (field.isUnsupported !== true) {
|
|
1649
|
+
fields.push(field);
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
const column = field.dbName ?? field.name;
|
|
1653
|
+
const parsed = parseUnsupportedPostgresType({ raw: field.nativeType ?? "" });
|
|
1654
|
+
const evidence = field.nativeTypeIdentity;
|
|
1655
|
+
if (evidence !== undefined && (evidence.source !== field.nativeType || parsed?.namespace !== undefined && parsed.namespace !== evidence.namespace)) {
|
|
1656
|
+
throw new VibeError9({ code: "VIBE_MIGRATION", message: "PostgreSQL type evidence no longer matches its declaration; recapture the independent reference", meta: { reason: "type-reference-invalid", shadowReusable: true, table: table.name, column } });
|
|
1657
|
+
}
|
|
1658
|
+
const prior = previous?.fields.find((entry) => (entry.dbName ?? entry.name) === column && entry.isUnsupported === true);
|
|
1659
|
+
const priorType = parseUnsupportedPostgresType({ raw: prior?.nativeType ?? "" });
|
|
1660
|
+
const priorNamespace = prior?.nativeTypeIdentity?.source === prior?.nativeType ? prior?.nativeTypeIdentity?.namespace : priorType?.namespace;
|
|
1661
|
+
let namespace = parsed?.namespace ?? evidence?.namespace ?? (priorType?.name === parsed?.name ? priorNamespace : undefined);
|
|
1662
|
+
if (params.mode === "catalog" || params.mode === "reference" || namespace === undefined) {
|
|
1663
|
+
const source = params.mode === "catalog" ? { table, column } : { type: { name: parsed?.name ?? "", ...namespace === undefined && params.mode === "reference" ? {} : { namespace: namespace ?? "pg_catalog" } } };
|
|
1664
|
+
const query = renderPostgresTypeIdentity({ dialect: postgresDialect3, source });
|
|
1665
|
+
const rows = parsed === undefined && params.mode !== "catalog" ? [] : await params.executor({ text: query.text, values: [...query.values] }).then((rows) => rows, (cause) => {
|
|
1666
|
+
throw new VibeError9({ code: "VIBE_MIGRATION", message: "PostgreSQL type identity inspection failed", meta: { reason: "type-inspection-unavailable", table: table.name, column }, cause });
|
|
1667
|
+
});
|
|
1668
|
+
const resolved = rows.length === 1 ? rows[0] : undefined;
|
|
1669
|
+
namespace = typeof resolved?.namespace === "string" && (resolved.name === parsed?.name || parsed === undefined && params.mode === "catalog") ? resolved.namespace : undefined;
|
|
1670
|
+
}
|
|
1671
|
+
if (parsed === undefined && params.mode !== "catalog" || namespace === undefined || field.nativeType === undefined) {
|
|
1672
|
+
unavailable.push({ kind: "column", table, column });
|
|
1673
|
+
fields.push(field);
|
|
1674
|
+
} else {
|
|
1675
|
+
fields.push({ ...field, nativeTypeIdentity: { source: field.nativeType, namespace } });
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
models.push({ ...model, fields });
|
|
1679
|
+
}
|
|
1680
|
+
return { schema: { ...params.schema, models }, unavailable };
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1546
1683
|
// packages/migrate/src/introspect/postgres-rls.ts
|
|
1547
|
-
import { VibeError as
|
|
1548
|
-
import { NATIVE_RLS, postgresDialect as
|
|
1684
|
+
import { VibeError as VibeError11, assertValidSchemaIR as assertValidSchemaIR3 } from "@vibeorm/schema";
|
|
1685
|
+
import { NATIVE_RLS, postgresDialect as postgresDialect5, renderRlsPolicy as renderRlsPolicy2 } from "@vibeorm/sql";
|
|
1549
1686
|
|
|
1550
1687
|
// packages/migrate/src/ddl/rls.ts
|
|
1551
|
-
import { VibeError as
|
|
1688
|
+
import { VibeError as VibeError10, assertValidSchemaIR as assertValidSchemaIR2, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
1552
1689
|
import * as sql from "@vibeorm/sql";
|
|
1553
1690
|
function isRlsProtected(params) {
|
|
1554
1691
|
return params.model.rls !== undefined && params.model.rls !== "unprotected";
|
|
@@ -1664,7 +1801,7 @@ function renderRlsStep(params) {
|
|
|
1664
1801
|
return [renderer(common).text];
|
|
1665
1802
|
}
|
|
1666
1803
|
default:
|
|
1667
|
-
throw new
|
|
1804
|
+
throw new VibeError10({ code: "VIBE_MIGRATION", message: "Expected a native RLS migration step" });
|
|
1668
1805
|
}
|
|
1669
1806
|
}
|
|
1670
1807
|
function securityChangesOf(params) {
|
|
@@ -1673,7 +1810,7 @@ function securityChangesOf(params) {
|
|
|
1673
1810
|
function assertSecurityAcknowledgement(params) {
|
|
1674
1811
|
if (params.securityChanges.length === 0 || params.acceptSecurityChanges === true)
|
|
1675
1812
|
return;
|
|
1676
|
-
throw new
|
|
1813
|
+
throw new VibeError10({
|
|
1677
1814
|
code: "VIBE_MIGRATION",
|
|
1678
1815
|
message: `Security changes require acceptSecurityChanges: true; acceptDataLoss does not authorize them:
|
|
1679
1816
|
${params.securityChanges.join(`
|
|
@@ -1700,19 +1837,19 @@ async function readPostgresRls(params) {
|
|
|
1700
1837
|
const tables = new Map;
|
|
1701
1838
|
for (const row of rows) {
|
|
1702
1839
|
const table = String(row.table_name);
|
|
1703
|
-
let
|
|
1704
|
-
if (
|
|
1705
|
-
|
|
1706
|
-
tables.set(table,
|
|
1840
|
+
let state = tables.get(table);
|
|
1841
|
+
if (state === undefined) {
|
|
1842
|
+
state = { table, enabled: row.enabled === true, force: row.force === true, policies: [] };
|
|
1843
|
+
tables.set(table, state);
|
|
1707
1844
|
}
|
|
1708
1845
|
if (row.policy_name !== null && row.policy_name !== undefined) {
|
|
1709
|
-
|
|
1846
|
+
state.policies.push({ name: String(row.policy_name), command: String(row.command), permissive: row.permissive === true, roles: String(row.roles), using: row.using_expression === null ? null : String(row.using_expression), withCheck: row.check_expression === null ? null : String(row.check_expression), comment: row.comment === null ? null : String(row.comment) });
|
|
1710
1847
|
}
|
|
1711
1848
|
}
|
|
1712
1849
|
return [...tables.values()];
|
|
1713
1850
|
}
|
|
1714
1851
|
function conflict(params) {
|
|
1715
|
-
throw new
|
|
1852
|
+
throw new VibeError11({
|
|
1716
1853
|
code: "VIBE_MIGRATION",
|
|
1717
1854
|
message: `Native RLS on public.${params.table} cannot be managed safely (${params.attributes.join(", ")}). Review the live policies and restore the deployed declaration, or remove conflicting policies explicitly with administrator credentials. Supply rlsSchema/previousSchema for the deployed policy; no policy is deleted or adopted automatically.`,
|
|
1718
1855
|
meta: params
|
|
@@ -1725,32 +1862,32 @@ async function verifyPostgresRls(params) {
|
|
|
1725
1862
|
await executor({ text: "SET LOCAL search_path = ''" });
|
|
1726
1863
|
const declared = new Map((schema?.models ?? []).map((model) => [model.dbName ?? model.name, model]));
|
|
1727
1864
|
let tables = await readPostgresRls({ executor });
|
|
1728
|
-
for (const
|
|
1729
|
-
const model = declared.get(
|
|
1730
|
-
if (!
|
|
1865
|
+
for (const state of tables) {
|
|
1866
|
+
const model = declared.get(state.table);
|
|
1867
|
+
if (!state.enabled && !state.force && state.policies.length === 0 && (model === undefined || !isRlsProtected({ model })))
|
|
1731
1868
|
continue;
|
|
1732
|
-
await executor({ text: `LOCK TABLE "public".${
|
|
1869
|
+
await executor({ text: `LOCK TABLE "public".${postgresDialect5.quoteIdent({ name: state.table })} IN ACCESS EXCLUSIVE MODE` });
|
|
1733
1870
|
}
|
|
1734
1871
|
tables = await readPostgresRls({ executor });
|
|
1735
1872
|
const matched = new Set;
|
|
1736
|
-
for (const
|
|
1737
|
-
const model = declared.get(
|
|
1738
|
-
const active =
|
|
1873
|
+
for (const state of tables) {
|
|
1874
|
+
const model = declared.get(state.table);
|
|
1875
|
+
const active = state.enabled || state.force || state.policies.length > 0;
|
|
1739
1876
|
if (schema === undefined && active)
|
|
1740
|
-
conflict({ table:
|
|
1877
|
+
conflict({ table: state.table, reason: "rls-unmanaged", attributes: ["security metadata cannot be exported losslessly without a trusted declaration"] });
|
|
1741
1878
|
if (model === undefined || !isRlsProtected({ model })) {
|
|
1742
1879
|
if (active)
|
|
1743
|
-
conflict({ table:
|
|
1880
|
+
conflict({ table: state.table, reason: "rls-conflict", attributes: ["unexpected native policies or RLS flags"] });
|
|
1744
1881
|
continue;
|
|
1745
1882
|
}
|
|
1746
1883
|
if (!active && params.requireInstalled !== true)
|
|
1747
1884
|
continue;
|
|
1748
|
-
const unknown =
|
|
1885
|
+
const unknown = state.policies.filter((policy) => policy.name !== NATIVE_RLS.policyName);
|
|
1749
1886
|
if (unknown.length > 0)
|
|
1750
|
-
conflict({ table:
|
|
1751
|
-
const policy =
|
|
1887
|
+
conflict({ table: state.table, reason: "rls-conflict", attributes: unknown.map((policy) => `unexpected policy ${policy.name}`) });
|
|
1888
|
+
const policy = state.policies.find((entry) => entry.name === NATIVE_RLS.policyName);
|
|
1752
1889
|
if (policy === undefined)
|
|
1753
|
-
conflict({ table:
|
|
1890
|
+
conflict({ table: state.table, reason: "rls-drift", attributes: ["missing policy"] });
|
|
1754
1891
|
const structure = [
|
|
1755
1892
|
...policy.command !== "*" ? ["command"] : [],
|
|
1756
1893
|
...!policy.permissive ? ["permissiveness"] : [],
|
|
@@ -1758,27 +1895,27 @@ async function verifyPostgresRls(params) {
|
|
|
1758
1895
|
...policy.withCheck === null ? ["missing WITH CHECK"] : []
|
|
1759
1896
|
];
|
|
1760
1897
|
if (structure.length > 0)
|
|
1761
|
-
conflict({ table:
|
|
1762
|
-
const attributes = [...!
|
|
1898
|
+
conflict({ table: state.table, reason: "rls-conflict", attributes: structure });
|
|
1899
|
+
const attributes = [...!state.enabled ? ["enabled"] : [], ...!state.force ? ["force"] : []];
|
|
1763
1900
|
await executor({ text: "SAVEPOINT vibeorm_rls_probe" });
|
|
1764
1901
|
let probe;
|
|
1765
1902
|
try {
|
|
1766
|
-
const rendered = renderRlsPolicy2({ dialect:
|
|
1903
|
+
const rendered = renderRlsPolicy2({ dialect: postgresDialect5, schema, model: model.name, policyName: PROBE });
|
|
1767
1904
|
await executor({ text: rendered.text });
|
|
1768
|
-
probe = (await readPostgresRls({ executor })).find((entry) => entry.table ===
|
|
1905
|
+
probe = (await readPostgresRls({ executor })).find((entry) => entry.table === state.table)?.policies.find((entry) => entry.name === PROBE);
|
|
1769
1906
|
} finally {
|
|
1770
1907
|
await executor({ text: "ROLLBACK TO SAVEPOINT vibeorm_rls_probe" });
|
|
1771
1908
|
await executor({ text: "RELEASE SAVEPOINT vibeorm_rls_probe" });
|
|
1772
1909
|
}
|
|
1773
1910
|
if (probe === undefined)
|
|
1774
|
-
conflict({ table:
|
|
1911
|
+
conflict({ table: state.table, reason: "rls-drift", attributes: ["probe unavailable"] });
|
|
1775
1912
|
if (policy.using !== probe.using)
|
|
1776
1913
|
attributes.push("using");
|
|
1777
1914
|
if (policy.withCheck !== probe.withCheck)
|
|
1778
1915
|
attributes.push("withCheck");
|
|
1779
1916
|
if (attributes.length > 0)
|
|
1780
|
-
conflict({ table:
|
|
1781
|
-
matched.add(
|
|
1917
|
+
conflict({ table: state.table, reason: "rls-drift", attributes });
|
|
1918
|
+
matched.add(state.table);
|
|
1782
1919
|
}
|
|
1783
1920
|
if (params.requireInstalled === true) {
|
|
1784
1921
|
for (const [table, model] of declared) {
|
|
@@ -1961,18 +2098,17 @@ async function introspectPostgres(params) {
|
|
|
1961
2098
|
if (params.inTransaction !== true)
|
|
1962
2099
|
await executor({ text: "BEGIN" });
|
|
1963
2100
|
try {
|
|
1964
|
-
const savedPath =
|
|
1965
|
-
const searchPath = savedPath
|
|
1966
|
-
if (
|
|
1967
|
-
throw new
|
|
2101
|
+
const savedPath = await executor(renderRlsReadinessQuery({ dialect: postgresDialect6, query: "readSearchPath" }));
|
|
2102
|
+
const searchPath = savedPath[0]?.searchPath;
|
|
2103
|
+
if (typeof searchPath !== "string")
|
|
2104
|
+
throw new VibeError12({ code: "VIBE_MIGRATION", message: "Cannot read the migration search_path" });
|
|
1968
2105
|
const matched = await verifyPostgresRls({ executor, ...params.rlsSchema === undefined ? {} : { schema: params.rlsSchema }, ...params.requireInstalledRls === undefined ? {} : { requireInstalled: params.requireInstalledRls } }).then((verified) => verified, (error) => {
|
|
1969
|
-
if (error instanceof
|
|
2106
|
+
if (error instanceof VibeError12)
|
|
1970
2107
|
throw error;
|
|
1971
|
-
throw new
|
|
2108
|
+
throw new VibeError12({ code: "VIBE_MIGRATION", message: "Native RLS verification could not complete. Check migration privileges and supply the deployed schema as previousSchema/rlsSchema before changing referenced columns.", meta: { reason: "rls-verification-unavailable" }, cause: error });
|
|
1972
2109
|
});
|
|
2110
|
+
await executor(renderRlsReadinessQuery({ dialect: postgresDialect6, query: "setSearchPath", searchPath }));
|
|
1973
2111
|
const physical = await introspectPhysicalPostgres({ executor });
|
|
1974
|
-
if (typeof searchPath === "string")
|
|
1975
|
-
await executor(renderRlsReadinessQuery({ dialect: postgresDialect5, query: "setSearchPath", searchPath }));
|
|
1976
2112
|
return projectPostgresRls({ physical, ...params.rlsSchema === undefined ? {} : { declared: params.rlsSchema }, matched });
|
|
1977
2113
|
} finally {
|
|
1978
2114
|
if (params.inTransaction !== true)
|
|
@@ -1990,7 +2126,7 @@ async function introspectPhysicalPostgres(params) {
|
|
|
1990
2126
|
executor({ text: ENUMS_QUERY }),
|
|
1991
2127
|
executor({ text: INDEXES_QUERY }),
|
|
1992
2128
|
executor({ text: CHECKS_QUERY }),
|
|
1993
|
-
executor({ text: renderExclusionCatalog2({ dialect:
|
|
2129
|
+
executor({ text: renderExclusionCatalog2({ dialect: postgresDialect6 }) })
|
|
1994
2130
|
]);
|
|
1995
2131
|
const tables = tableRows.map((row) => row.table_name);
|
|
1996
2132
|
const views = viewRows.map((row) => row.table_name);
|
|
@@ -2158,12 +2294,15 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2158
2294
|
}
|
|
2159
2295
|
addFkRelations({ models, fkGroups, joinTables });
|
|
2160
2296
|
addJoinTableRelations({ models, joinTables });
|
|
2161
|
-
|
|
2297
|
+
const resolved = await resolvePostgresTypeIdentities({ executor, mode: "catalog", schema: {
|
|
2162
2298
|
datasource: { provider: "postgresql" },
|
|
2163
2299
|
models,
|
|
2164
2300
|
enums,
|
|
2165
2301
|
extensions: []
|
|
2166
|
-
};
|
|
2302
|
+
} });
|
|
2303
|
+
if (resolved.unavailable.length > 0)
|
|
2304
|
+
throw new VibeError12({ code: "VIBE_MIGRATION", message: "PostgreSQL column type identity could not be read", meta: { reason: "type-inspection-unavailable", columns: resolved.unavailable } });
|
|
2305
|
+
return resolved.schema;
|
|
2167
2306
|
}
|
|
2168
2307
|
function buildModel(params) {
|
|
2169
2308
|
const { table, isView, columns, pkByTable, uniquesByConstraint, constraintNames, indexes, enumNames } = params;
|
|
@@ -2246,27 +2385,6 @@ function buildField(params) {
|
|
|
2246
2385
|
...mapped.isUnsupported === true ? { isUnsupported: true } : {}
|
|
2247
2386
|
};
|
|
2248
2387
|
}
|
|
2249
|
-
var CANONICAL_UDT = {
|
|
2250
|
-
text: "String",
|
|
2251
|
-
bool: "Boolean",
|
|
2252
|
-
int4: "Int",
|
|
2253
|
-
int8: "BigInt",
|
|
2254
|
-
float8: "Float",
|
|
2255
|
-
numeric: "Decimal",
|
|
2256
|
-
timestamptz: "DateTime",
|
|
2257
|
-
jsonb: "Json",
|
|
2258
|
-
bytea: "Bytes"
|
|
2259
|
-
};
|
|
2260
|
-
var NON_CANONICAL_UDT = {
|
|
2261
|
-
varchar: "String",
|
|
2262
|
-
bpchar: "String",
|
|
2263
|
-
uuid: "String",
|
|
2264
|
-
int2: "Int",
|
|
2265
|
-
float4: "Float",
|
|
2266
|
-
timestamp: "DateTime",
|
|
2267
|
-
date: "DateTime",
|
|
2268
|
-
json: "Json"
|
|
2269
|
-
};
|
|
2270
2388
|
function reverseMapType(params) {
|
|
2271
2389
|
const { column, enumNames } = params;
|
|
2272
2390
|
const isList = column.dataType === "ARRAY";
|
|
@@ -2322,8 +2440,10 @@ function withModifier(params) {
|
|
|
2322
2440
|
}
|
|
2323
2441
|
if (udt === "bpchar")
|
|
2324
2442
|
return "char";
|
|
2325
|
-
if (udt === "
|
|
2326
|
-
return
|
|
2443
|
+
if ((udt === "bit" || udt === "varbit") && column.charMaxLength !== null)
|
|
2444
|
+
return `${udt}(${column.charMaxLength})`;
|
|
2445
|
+
if ((udt === "timestamp" || udt === "time" || udt === "timetz") && column.datetimePrecision !== null && column.datetimePrecision !== 6) {
|
|
2446
|
+
return `${udt}(${column.datetimePrecision})`;
|
|
2327
2447
|
}
|
|
2328
2448
|
return udt;
|
|
2329
2449
|
}
|
|
@@ -3132,8 +3252,8 @@ async function readIndexes(params) {
|
|
|
3132
3252
|
}
|
|
3133
3253
|
let where;
|
|
3134
3254
|
if (partial) {
|
|
3135
|
-
const
|
|
3136
|
-
const match =
|
|
3255
|
+
const sql = indexSqlByName.get(name);
|
|
3256
|
+
const match = sql?.match(/\sWHERE\s+(.*)$/is);
|
|
3137
3257
|
where = match?.[1]?.trim();
|
|
3138
3258
|
}
|
|
3139
3259
|
tableIndexes.push({
|
|
@@ -3164,17 +3284,17 @@ async function readIndexes(params) {
|
|
|
3164
3284
|
var PLAIN_INDEX_COLUMN = /^"((?:[^"]|"")+)"(?:\s+(ASC|DESC))?$/i;
|
|
3165
3285
|
var CAST_REAL_INDEX_COLUMN = /^CAST\s*\(\s*"((?:[^"]|"")+)"\s+AS\s+REAL\s*\)(?:\s+(ASC|DESC))?$/i;
|
|
3166
3286
|
function parseIndexColumnsFromSql(params) {
|
|
3167
|
-
const { sql
|
|
3168
|
-
if (
|
|
3287
|
+
const { sql } = params;
|
|
3288
|
+
if (sql === null)
|
|
3169
3289
|
return null;
|
|
3170
|
-
const onMatch = /\sON\s+"(?:[^"]|"")+"\s*\(/i.exec(
|
|
3290
|
+
const onMatch = /\sON\s+"(?:[^"]|"")+"\s*\(/i.exec(sql);
|
|
3171
3291
|
if (onMatch === null)
|
|
3172
3292
|
return null;
|
|
3173
3293
|
const open = onMatch.index + onMatch[0].length - 1;
|
|
3174
3294
|
let listDepth = 0;
|
|
3175
3295
|
let close = -1;
|
|
3176
|
-
for (let index = open;index <
|
|
3177
|
-
const char =
|
|
3296
|
+
for (let index = open;index < sql.length; index++) {
|
|
3297
|
+
const char = sql[index];
|
|
3178
3298
|
if (char === "(")
|
|
3179
3299
|
listDepth += 1;
|
|
3180
3300
|
else if (char === ")") {
|
|
@@ -3187,7 +3307,7 @@ function parseIndexColumnsFromSql(params) {
|
|
|
3187
3307
|
}
|
|
3188
3308
|
if (close === -1)
|
|
3189
3309
|
return null;
|
|
3190
|
-
const list =
|
|
3310
|
+
const list = sql.slice(open + 1, close);
|
|
3191
3311
|
const terms = [];
|
|
3192
3312
|
let depth = 0;
|
|
3193
3313
|
let start = 0;
|
|
@@ -3328,7 +3448,7 @@ function q(name) {
|
|
|
3328
3448
|
}
|
|
3329
3449
|
|
|
3330
3450
|
// packages/migrate/src/execute.ts
|
|
3331
|
-
import { VibeError as
|
|
3451
|
+
import { VibeError as VibeError13 } from "@vibeorm/schema";
|
|
3332
3452
|
var SELF_MANAGED_MARKER = "PRAGMA foreign_keys=OFF";
|
|
3333
3453
|
var FK_CHECK_PREFIX = "PRAGMA foreign_key_check";
|
|
3334
3454
|
function usesSelfManagedTransactions(params) {
|
|
@@ -3349,9 +3469,9 @@ function classifySelfManagedStatement(params) {
|
|
|
3349
3469
|
return "plain";
|
|
3350
3470
|
}
|
|
3351
3471
|
function selfManagedResumeStart(params) {
|
|
3352
|
-
const { sql
|
|
3472
|
+
const { sql, start } = params;
|
|
3353
3473
|
for (let index = start - 1;index >= 0; index -= 1) {
|
|
3354
|
-
const control = classifySelfManagedStatement({ statement:
|
|
3474
|
+
const control = classifySelfManagedStatement({ statement: sql[index] });
|
|
3355
3475
|
if (control === "endTransaction" || control === "abandon" || control === "fkOn")
|
|
3356
3476
|
return start;
|
|
3357
3477
|
if (control === "fkOff")
|
|
@@ -3369,7 +3489,7 @@ function assertSelfManagedResumeSafe(params) {
|
|
|
3369
3489
|
const completedIndex = params.direction === "up" ? params.start + 1 : -(params.start + 2);
|
|
3370
3490
|
const rewind = selfManagedResumeStart({ sql: params.sql, start: params.start });
|
|
3371
3491
|
const retryIndex = params.direction === "up" ? rewind : -(rewind + 1);
|
|
3372
|
-
throw new
|
|
3492
|
+
throw new VibeError13({
|
|
3373
3493
|
code: "VIBE_MIGRATION",
|
|
3374
3494
|
message: `migration "${params.migration}" has a legacy sqlite checkpoint at ${params.direction} COMMIT (statement ${params.start + 1}). The block may already have committed; replay is refused. Inspect the block's effects. If it committed, set its journal statement_index to ${completedIndex} and error to NULL to resume the remaining statements. Only if the entire block did not commit, set statement_index to ${retryIndex} and error to NULL to retry the block. Do not mark the whole migration applied unless all remaining statements also ran.`,
|
|
3375
3495
|
meta: { reason: "uncertain-statement", migration: params.migration, statementIndex: params.start, direction: params.direction, dialect: "sqlite" }
|
|
@@ -3398,37 +3518,37 @@ function unsupportedScript(params) {
|
|
|
3398
3518
|
const { migration, direction, index, problem, found } = params;
|
|
3399
3519
|
const subject = direction === "down" ? `the down script of migration "${migration}"` : `migration "${migration}"`;
|
|
3400
3520
|
const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${params.statements}`;
|
|
3401
|
-
return new
|
|
3521
|
+
return new VibeError13({
|
|
3402
3522
|
code: "VIBE_MIGRATION",
|
|
3403
3523
|
message: `${subject} manages its own sqlite transactions, and ${position} breaks the shape recovery depends on: ${found}. ` + `A table-rebuild block must be written exactly as \`${SUPPORTED_BLOCK_SHAPE}\`. ` + "Inside that block a crash rolls everything back, so a resume can replay it safely. " + "A statement that sits in a foreign-keys-off region with no transaction around it commits on its own, " + "and the resume would run it a second time with no error raised either time. " + "Nothing has been executed. Move the statement after `PRAGMA foreign_keys=ON`, where it commits together " + "with its own journal checkpoint, or give it its own `BEGIN;` and `COMMIT;`. " + 'See "Self-managed sqlite scripts" in docs/migrations.md.',
|
|
3404
3524
|
meta: { reason: "unsupported-self-managed-script", problem, migration, statementIndex: index, direction }
|
|
3405
3525
|
});
|
|
3406
3526
|
}
|
|
3407
3527
|
function assertSupportedSelfManagedScript(params) {
|
|
3408
|
-
const { sql
|
|
3409
|
-
const statements =
|
|
3528
|
+
const { sql, migration, direction } = params;
|
|
3529
|
+
const statements = sql.length;
|
|
3410
3530
|
const refuse = (index, problem, found) => unsupportedScript({ migration, direction, index, statements, problem, found });
|
|
3411
|
-
let
|
|
3531
|
+
let state = "outside";
|
|
3412
3532
|
let openedAt = -1;
|
|
3413
3533
|
for (let index = 0;index < statements; index += 1) {
|
|
3414
|
-
const control = classifySelfManagedStatement({ statement:
|
|
3415
|
-
if (
|
|
3534
|
+
const control = classifySelfManagedStatement({ statement: sql[index] });
|
|
3535
|
+
if (state === "fkOff") {
|
|
3416
3536
|
if (control !== "begin") {
|
|
3417
3537
|
throw refuse(index, "fk-off-without-transaction", "`PRAGMA foreign_keys=OFF` is not followed by `BEGIN;`");
|
|
3418
3538
|
}
|
|
3419
|
-
|
|
3539
|
+
state = "inBlock";
|
|
3420
3540
|
continue;
|
|
3421
3541
|
}
|
|
3422
|
-
if (
|
|
3542
|
+
if (state === "committed") {
|
|
3423
3543
|
if (control !== "fkOn") {
|
|
3424
3544
|
throw refuse(index, "commit-without-restore", "`COMMIT;` is not followed by `PRAGMA foreign_keys=ON;`");
|
|
3425
3545
|
}
|
|
3426
|
-
|
|
3546
|
+
state = "outside";
|
|
3427
3547
|
continue;
|
|
3428
3548
|
}
|
|
3429
|
-
if (
|
|
3549
|
+
if (state === "inBlock") {
|
|
3430
3550
|
if (control === "endTransaction") {
|
|
3431
|
-
|
|
3551
|
+
state = "committed";
|
|
3432
3552
|
continue;
|
|
3433
3553
|
}
|
|
3434
3554
|
if (control === "plain")
|
|
@@ -3436,7 +3556,7 @@ function assertSupportedSelfManagedScript(params) {
|
|
|
3436
3556
|
throw refuse(index, "unsupported-control-in-block", "transaction or pragma control appears inside an open rebuild block");
|
|
3437
3557
|
}
|
|
3438
3558
|
if (control === "fkOff") {
|
|
3439
|
-
|
|
3559
|
+
state = "fkOff";
|
|
3440
3560
|
openedAt = index;
|
|
3441
3561
|
continue;
|
|
3442
3562
|
}
|
|
@@ -3444,8 +3564,8 @@ function assertSupportedSelfManagedScript(params) {
|
|
|
3444
3564
|
throw refuse(index, "transaction-control-outside-block", "transaction control appears outside any rebuild block");
|
|
3445
3565
|
}
|
|
3446
3566
|
}
|
|
3447
|
-
if (
|
|
3448
|
-
throw refuse(openedAt,
|
|
3567
|
+
if (state !== "outside") {
|
|
3568
|
+
throw refuse(openedAt, state === "committed" ? "commit-without-restore" : "unclosed-block", state === "committed" ? "the block commits but never restores foreign keys with `PRAGMA foreign_keys=ON;`" : "`PRAGMA foreign_keys=OFF` is never closed by `COMMIT;` and `PRAGMA foreign_keys=ON;`");
|
|
3449
3569
|
}
|
|
3450
3570
|
}
|
|
3451
3571
|
async function executeStatement(params) {
|
|
@@ -3454,7 +3574,7 @@ async function executeStatement(params) {
|
|
|
3454
3574
|
const rows = await executor({ text: statement, values: [] });
|
|
3455
3575
|
if (rows.length > 0) {
|
|
3456
3576
|
const sample = rows.slice(0, 5).map((row) => `${String(row.table)} rowid ${String(row.rowid)} \u2192 ${String(row.parent)}`).join("; ");
|
|
3457
|
-
throw new
|
|
3577
|
+
throw new VibeError13({
|
|
3458
3578
|
code: "VIBE_MIGRATION",
|
|
3459
3579
|
message: `foreign key check failed after table rebuild: ${rows.length} violation(s) \u2014 ${sample}`,
|
|
3460
3580
|
meta: { violations: rows }
|
|
@@ -3474,8 +3594,8 @@ async function cleanupSelfManagedFailure(params) {
|
|
|
3474
3594
|
}
|
|
3475
3595
|
|
|
3476
3596
|
// packages/migrate/src/postgres-default-equivalence.ts
|
|
3477
|
-
import { VibeError as
|
|
3478
|
-
import { postgresDialect as
|
|
3597
|
+
import { VibeError as VibeError14 } from "@vibeorm/schema";
|
|
3598
|
+
import { postgresDialect as postgresDialect7, renderPostgresDefaultFunctionIdentity } from "@vibeorm/sql";
|
|
3479
3599
|
var IDENTIFIER = '(?:"(?:[^"\\u0000]|"")+"|[A-Za-z_][A-Za-z_0-9$]*)';
|
|
3480
3600
|
var CALL = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*\\(\\s*\\)\\s*$`);
|
|
3481
3601
|
function parseCall(params) {
|
|
@@ -3491,9 +3611,9 @@ async function equivalentPostgresDefaultFunction(params) {
|
|
|
3491
3611
|
const observed = parseCall({ expression: params.actual });
|
|
3492
3612
|
if (expected?.namespace === undefined || observed === undefined)
|
|
3493
3613
|
return false;
|
|
3494
|
-
const query = renderPostgresDefaultFunctionIdentity({ dialect:
|
|
3614
|
+
const query = renderPostgresDefaultFunctionIdentity({ dialect: postgresDialect7, table: params.table, column: params.column });
|
|
3495
3615
|
const rows = await params.executor({ text: query.text, values: [...query.values] }).catch((cause) => {
|
|
3496
|
-
throw new
|
|
3616
|
+
throw new VibeError14({ code: "VIBE_MIGRATION", message: "Cannot inspect the stored default function identity", meta: { reason: "default-inspection-unavailable", table: params.table, column: params.column }, cause });
|
|
3497
3617
|
});
|
|
3498
3618
|
if (rows.length !== 1)
|
|
3499
3619
|
return false;
|
|
@@ -3545,9 +3665,17 @@ async function inspectSqlObject(params) {
|
|
|
3545
3665
|
} else {
|
|
3546
3666
|
const constraintType = params.identity.constraintType;
|
|
3547
3667
|
const constraint = sqliteTableConstraints2({ sql: String(facts.definition ?? "") }).find((entry) => entry.name === name && entry.type === constraintType);
|
|
3548
|
-
if (constraint === undefined)
|
|
3549
|
-
|
|
3550
|
-
|
|
3668
|
+
if (constraint === undefined && constraintType === "primaryKey" && name === `${params.identity.table.name}_pkey`) {
|
|
3669
|
+
const query = renderSqliteColumnInventory({ dialect: getSqlDialect({ dialect: "sqlite" }), namespace: params.identity.table.namespace, table: params.identity.table.name });
|
|
3670
|
+
const columns = (await params.executor({ text: query.text, values: [...query.values] })).filter((column) => Number(column.pk) > 0).sort((a, b) => Number(a.pk) - Number(b.pk));
|
|
3671
|
+
if (columns.length === 0)
|
|
3672
|
+
return { state: "missing" };
|
|
3673
|
+
facts = { type: "primaryKey", columns: columns.map((column) => String(column.name)) };
|
|
3674
|
+
} else {
|
|
3675
|
+
if (constraint === undefined)
|
|
3676
|
+
return { state: "unavailable", reason: "constraint-definition-inspection-unavailable" };
|
|
3677
|
+
facts = { type: constraint.type, definition: constraint.sql };
|
|
3678
|
+
}
|
|
3551
3679
|
}
|
|
3552
3680
|
}
|
|
3553
3681
|
if (params.dialect === "sqlite" && (params.identity.kind === "column" || params.identity.kind === "columnDefault" || params.identity.kind === "generatedColumn")) {
|
|
@@ -3561,34 +3689,84 @@ async function inspectSqlObject(params) {
|
|
|
3561
3689
|
if (params.identity.kind === "columnDefault")
|
|
3562
3690
|
facts = { definition: facts.definition ?? null };
|
|
3563
3691
|
if (params.dialect === "postgres") {
|
|
3564
|
-
const
|
|
3565
|
-
const
|
|
3566
|
-
|
|
3567
|
-
return { state: "unavailable", reason: "dependency-inspection-unavailable" };
|
|
3568
|
-
facts = { ...facts, dependencyAddresses: rows2.map((row) => String(row.address)).sort() };
|
|
3692
|
+
const query = renderMigrationDependencies({ dialect: getSqlDialect({ dialect: params.dialect }), identity: params.identity });
|
|
3693
|
+
const rows = await params.executor({ text: query.text, values: [...query.values] });
|
|
3694
|
+
return postgresInspection({ identity: params.identity, facts, addresses: rows.map((row) => row.address) });
|
|
3569
3695
|
}
|
|
3570
3696
|
if (params.dialect === "mysql") {
|
|
3571
|
-
const
|
|
3572
|
-
if (
|
|
3573
|
-
const
|
|
3574
|
-
if (params.identity.kind === "function" &&
|
|
3697
|
+
const query = renderMysqlObjectMembers({ dialect: getSqlDialect({ dialect: params.dialect }), identity: params.identity });
|
|
3698
|
+
if (query !== undefined) {
|
|
3699
|
+
const rows = await params.executor({ text: query.text, values: [...query.values] });
|
|
3700
|
+
if (params.identity.kind === "function" && rows.length === 0)
|
|
3575
3701
|
return { state: "unavailable", reason: "function-signature-unavailable" };
|
|
3576
3702
|
if (params.identity.kind === "function") {
|
|
3577
|
-
const signature =
|
|
3703
|
+
const signature = rows.filter((row) => row.mode !== null).map((row) => ({ namespace: "mysql", name: String(row.type) }));
|
|
3578
3704
|
if (canonicalMigrationJson({ value: signature }) !== canonicalMigrationJson({ value: params.identity.argumentTypes }))
|
|
3579
3705
|
return { state: "missing" };
|
|
3580
3706
|
}
|
|
3581
|
-
facts = { ...facts, members:
|
|
3707
|
+
facts = { ...facts, members: rows.map((row) => canonicalMigrationJson({ value: row })) };
|
|
3582
3708
|
}
|
|
3583
3709
|
}
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3710
|
+
return { state: "present", definition: { identity: params.identity, facts, dependencies: [], format: "catalog-v2" } };
|
|
3711
|
+
}).catch((error) => ({ state: "unavailable", reason: error instanceof VibeError15 ? String(error.meta.feature ?? error.code) : "catalog-query-failed" }));
|
|
3712
|
+
}
|
|
3713
|
+
function postgresInspection(params) {
|
|
3714
|
+
if (!params.addresses.every((address) => typeof address === "string"))
|
|
3715
|
+
return { state: "unavailable", reason: "dependency-inspection-unavailable" };
|
|
3716
|
+
const facts = { ...params.identity.kind === "columnDefault" ? { definition: params.facts.definition ?? null } : params.facts, dependencyAddresses: [...params.addresses].sort() };
|
|
3717
|
+
const dependencies = [];
|
|
3718
|
+
if (params.identity.kind === "trigger") {
|
|
3719
|
+
if (params.facts.functionArguments !== "")
|
|
3720
|
+
return { state: "unavailable", reason: "trigger-function-signature-unavailable" };
|
|
3721
|
+
dependencies.push({ kind: "function", name: { namespace: String(params.facts.functionNamespace), name: String(params.facts.functionName) }, argumentTypes: [] });
|
|
3722
|
+
}
|
|
3723
|
+
return { state: "present", definition: { identity: params.identity, facts, dependencies, format: "catalog-v2" } };
|
|
3724
|
+
}
|
|
3725
|
+
function unavailableReason(params) {
|
|
3726
|
+
return params.error instanceof VibeError15 ? String(params.error.meta.feature ?? params.error.meta.reason ?? params.error.code) : "catalog-query-failed";
|
|
3727
|
+
}
|
|
3728
|
+
async function readBatch(params) {
|
|
3729
|
+
const positions = new Set(params.batch.positions);
|
|
3730
|
+
return Promise.resolve().then(async () => {
|
|
3731
|
+
const rows = new Map;
|
|
3732
|
+
for (const row of await params.executor({ text: params.batch.text, values: [...params.batch.values] })) {
|
|
3733
|
+
if (typeof row.position !== "number" || !Number.isInteger(row.position) || !positions.has(row.position))
|
|
3734
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Invalid batched catalog identity", meta: { reason: "catalog-query-failed" } });
|
|
3735
|
+
const matches = rows.get(row.position) ?? [];
|
|
3736
|
+
matches.push(row);
|
|
3737
|
+
rows.set(row.position, matches);
|
|
3738
|
+
}
|
|
3739
|
+
return { ok: true, rows };
|
|
3740
|
+
}).then((outcome) => outcome, (error) => ({ ok: false, reason: unavailableReason({ error }) }));
|
|
3741
|
+
}
|
|
3742
|
+
function batchedInspection(params) {
|
|
3743
|
+
if (params.matches.length === 0)
|
|
3744
|
+
return { state: "missing" };
|
|
3745
|
+
if (params.matches.length !== 1)
|
|
3746
|
+
return { state: "unavailable", reason: "ambiguous-catalog-identity" };
|
|
3747
|
+
const row = params.matches[0];
|
|
3748
|
+
const facts = row.facts !== null && typeof row.facts === "object" && !Array.isArray(row.facts) ? factsOf({ row: row.facts }) : undefined;
|
|
3749
|
+
if (facts === undefined || !Array.isArray(row.dependencyAddresses))
|
|
3750
|
+
return { state: "unavailable", reason: "unsupported-catalog-value" };
|
|
3751
|
+
return postgresInspection({ identity: params.identity, facts, addresses: row.dependencyAddresses });
|
|
3752
|
+
}
|
|
3753
|
+
async function inspectSqlObjects(params) {
|
|
3754
|
+
if (params.dialect !== "postgres") {
|
|
3755
|
+
const results = [];
|
|
3756
|
+
for (const identity of params.identities)
|
|
3757
|
+
results.push(await inspectSqlObject({ ...params, identity }));
|
|
3758
|
+
return results;
|
|
3759
|
+
}
|
|
3760
|
+
return Promise.resolve().then(async () => {
|
|
3761
|
+
const results = params.identities.map(() => ({ state: "unavailable", reason: "catalog-query-failed" }));
|
|
3762
|
+
for (const batch of renderMigrationObjectBatch({ dialect: getSqlDialect({ dialect: params.dialect }), identities: params.identities })) {
|
|
3763
|
+
const outcome = await readBatch({ executor: params.executor, batch });
|
|
3764
|
+
for (const position of batch.positions) {
|
|
3765
|
+
results[position] = outcome.ok ? batchedInspection({ identity: params.identities[position], matches: outcome.rows.get(position) ?? [] }) : { state: "unavailable", reason: outcome.reason };
|
|
3766
|
+
}
|
|
3589
3767
|
}
|
|
3590
|
-
return
|
|
3591
|
-
}).
|
|
3768
|
+
return results;
|
|
3769
|
+
}).then((result) => result, (error) => params.identities.map(() => ({ state: "unavailable", reason: unavailableReason({ error }) })));
|
|
3592
3770
|
}
|
|
3593
3771
|
async function assertCatalogVisibility(params) {
|
|
3594
3772
|
if (params.dialect !== "mysql")
|
|
@@ -3598,15 +3776,23 @@ async function assertCatalogVisibility(params) {
|
|
|
3598
3776
|
const grants = new Set(rows.map((row) => row.privilege));
|
|
3599
3777
|
const missing = ["SELECT", "TRIGGER", "SHOW VIEW", "EXECUTE"].filter((privilege) => !grants.has(privilege));
|
|
3600
3778
|
if (missing.length > 0)
|
|
3601
|
-
throw new
|
|
3779
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "MySQL catalog visibility requires direct database-wide grants; role-only or partial grants cannot prove object absence", meta: { reason: "inventory-unavailable", missing } });
|
|
3602
3780
|
}
|
|
3781
|
+
var CATALOG_V1_OMITTED_FACTS = Object.freeze({
|
|
3782
|
+
function: ["definerOwner"],
|
|
3783
|
+
column: ["collationNamespace", "collationName"],
|
|
3784
|
+
generatedColumn: ["collationNamespace", "collationName"]
|
|
3785
|
+
});
|
|
3603
3786
|
function compare(params) {
|
|
3604
3787
|
const differences = [];
|
|
3788
|
+
const omitted = params.expected.format === "catalog-v1" ? CATALOG_V1_OMITTED_FACTS[params.expected.identity.kind] ?? [] : [];
|
|
3605
3789
|
for (const key of new Set([...Object.keys(params.expected.facts), ...Object.keys(params.live.facts)])) {
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3790
|
+
if (omitted.includes(key) && !Object.hasOwn(params.expected.facts, key))
|
|
3791
|
+
continue;
|
|
3792
|
+
const expected = params.expected.facts[key];
|
|
3793
|
+
const actual = params.live.facts[key];
|
|
3794
|
+
if (expected === undefined !== (actual === undefined) || canonicalMigrationJson({ value: expected }) !== canonicalMigrationJson({ value: actual }))
|
|
3795
|
+
differences.push({ attribute: key, expected, actual });
|
|
3610
3796
|
}
|
|
3611
3797
|
const expected = params.expected.dependencies.map((identity) => sqlObjectKey4({ identity })).sort();
|
|
3612
3798
|
const actual = params.live.dependencies.map((identity) => sqlObjectKey4({ identity })).sort();
|
|
@@ -3616,7 +3802,7 @@ function compare(params) {
|
|
|
3616
3802
|
}
|
|
3617
3803
|
function assertProbeMode(params) {
|
|
3618
3804
|
if (params.probe !== undefined && params.probe !== "none" && DIALECT_CAPABILITIES2[params.dialect].checkNormalization !== params.probe)
|
|
3619
|
-
throw new
|
|
3805
|
+
throw new VibeError15({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `${params.probe} CHECK probes are unavailable on ${params.dialect}`, meta: { feature: "checkProbe", dialect: params.dialect } });
|
|
3620
3806
|
}
|
|
3621
3807
|
async function verifyMigrationState(params) {
|
|
3622
3808
|
return verifyState(params);
|
|
@@ -3630,10 +3816,13 @@ async function verifyState(params) {
|
|
|
3630
3816
|
for (const object of declarations)
|
|
3631
3817
|
identities.set(sqlObjectKey4({ identity: object.identity }), object.identity);
|
|
3632
3818
|
const objects = [];
|
|
3819
|
+
const inspections = params.batch === true ? await inspectSqlObjects({ ...params, identities: [...identities.values()] }) : undefined;
|
|
3820
|
+
let position = 0;
|
|
3633
3821
|
for (const [key, identity] of identities) {
|
|
3634
3822
|
const ownership = owned.has(key) ? "migration" : "schema";
|
|
3635
3823
|
const expected = definitions.get(key);
|
|
3636
|
-
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3824
|
+
const live = inspections === undefined ? await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity }) : inspections[position];
|
|
3825
|
+
position++;
|
|
3637
3826
|
if (live.state !== "present") {
|
|
3638
3827
|
objects.push({ identity, ownership, state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} });
|
|
3639
3828
|
} else if (expected === undefined) {
|
|
@@ -3653,9 +3842,10 @@ async function verifyState(params) {
|
|
|
3653
3842
|
}
|
|
3654
3843
|
async function verifySchemaObjects(params) {
|
|
3655
3844
|
const physical = params.dialect === "postgres" ? await introspectPhysicalPostgres({ executor: params.executor }) : params.dialect === "mysql" ? await introspectMysql({ executor: params.executor }) : params.sqliteRestorePending === true ? await introspectSqliteBeforeForeignKeyRestore({ executor: params.executor }) : await introspectSqlite({ executor: params.executor });
|
|
3656
|
-
const
|
|
3845
|
+
const resolved = params.dialect === "postgres" ? await resolvePostgresTypeIdentities({ executor: params.executor, schema: params.expected.schema, mode: "verify" }) : { schema: params.expected.schema, unavailable: [] };
|
|
3846
|
+
const expected = normalizeSchema({ schema: resolved.schema, dialect: params.dialect });
|
|
3657
3847
|
const actual = normalizeSchema({ schema: physical, dialect: params.dialect });
|
|
3658
|
-
const objects = [];
|
|
3848
|
+
const objects = resolved.unavailable.map((identity) => ({ identity, ownership: "schema", state: "unavailable", reason: "type-reference-required", differences: [] }));
|
|
3659
3849
|
const namespace = await migrationNamespace({ executor: params.executor, dialect: params.dialect });
|
|
3660
3850
|
const sqliteTableOptions = params.dialect === "sqlite" && expected.tables.size > 0 ? await params.executor({ text: "PRAGMA main.table_list;", values: [] }) : undefined;
|
|
3661
3851
|
if (params.dialect === "postgres")
|
|
@@ -3690,9 +3880,9 @@ async function verifySchemaObjects(params) {
|
|
|
3690
3880
|
}
|
|
3691
3881
|
}
|
|
3692
3882
|
const differences = [];
|
|
3693
|
-
const pin = (attribute,
|
|
3694
|
-
if (canonicalMigrationJson({ value:
|
|
3695
|
-
differences.push({ attribute, expected
|
|
3883
|
+
const pin = (attribute, expected, actual) => {
|
|
3884
|
+
if (canonicalMigrationJson({ value: expected }) !== canonicalMigrationJson({ value: actual }))
|
|
3885
|
+
differences.push({ attribute, expected, actual });
|
|
3696
3886
|
};
|
|
3697
3887
|
pin("primaryKey", desired.pk, live.pk);
|
|
3698
3888
|
for (const [column, field] of desired.columns) {
|
|
@@ -3722,8 +3912,8 @@ async function verifySchemaObjects(params) {
|
|
|
3722
3912
|
if (inspection.state !== "present") {
|
|
3723
3913
|
objects.push({ identity, ownership: "schema", state: inspection.state, differences: [], ...inspection.state === "unavailable" ? { reason: inspection.reason } : {} });
|
|
3724
3914
|
} else {
|
|
3725
|
-
const
|
|
3726
|
-
objects.push({ identity, ownership: "schema", state:
|
|
3915
|
+
const differences = ["validated", "triggersEnabled"].flatMap((attribute) => inspection.definition.facts[attribute] === true ? [] : [{ attribute, expected: true, actual: inspection.definition.facts[attribute] }]);
|
|
3916
|
+
objects.push({ identity, ownership: "schema", state: differences.length === 0 ? "matching" : "changed", differences });
|
|
3727
3917
|
}
|
|
3728
3918
|
}
|
|
3729
3919
|
}
|
|
@@ -3738,8 +3928,8 @@ async function verifySchemaObjects(params) {
|
|
|
3738
3928
|
if (inspection.state !== "present") {
|
|
3739
3929
|
objects.push({ identity, ownership: "schema", state: inspection.state, differences: [], ...inspection.state === "unavailable" ? { reason: inspection.reason } : {} });
|
|
3740
3930
|
} else {
|
|
3741
|
-
const
|
|
3742
|
-
objects.push({ identity, ownership: "schema", state:
|
|
3931
|
+
const differences = ["valid", "ready"].flatMap((attribute) => inspection.definition.facts[attribute] === true ? [] : [{ attribute, expected: true, actual: inspection.definition.facts[attribute] }]);
|
|
3932
|
+
objects.push({ identity, ownership: "schema", state: differences.length === 0 ? "matching" : "changed", differences });
|
|
3743
3933
|
}
|
|
3744
3934
|
}
|
|
3745
3935
|
objects.push({ identity: { kind: "table", name: table }, ownership: "schema", state: differences.length === 0 ? "matching" : "changed", differences });
|
|
@@ -3771,14 +3961,14 @@ async function verifySchemaObjects(params) {
|
|
|
3771
3961
|
if (params.dialect === "postgres" && params.probe === "rollback") {
|
|
3772
3962
|
const normalized = await deparsePostgresCheck({ executor: params.executor, table, expression, probe: "rollback" });
|
|
3773
3963
|
const read = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3774
|
-
const
|
|
3775
|
-
objects.push({ identity, ownership: "schema", state: normalized ===
|
|
3964
|
+
const actual = read.state === "present" ? read.definition.facts.definition : undefined;
|
|
3965
|
+
objects.push({ identity, ownership: "schema", state: normalized === actual ? "matching" : "changed", differences: normalized === actual ? [] : [{ attribute: "definition", expected: normalized, actual }] });
|
|
3776
3966
|
} else if (params.dialect === "mysql" && params.probe === "temporary") {
|
|
3777
3967
|
const normalized = await deparseMysqlCheck({ executor: params.executor, table, expression, probe: "temporary" });
|
|
3778
3968
|
const inspection = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3779
|
-
const
|
|
3780
|
-
const match = normalized ===
|
|
3781
|
-
objects.push({ identity, ownership: "schema", state: inspection.state === "unavailable" ? "unavailable" : match ? "matching" : "changed", differences: match ? [] : [{ attribute: "definition", expected: normalized, actual
|
|
3969
|
+
const actual = inspection.state === "present" ? inspection.definition.facts.definition : undefined;
|
|
3970
|
+
const match = normalized === actual;
|
|
3971
|
+
objects.push({ identity, ownership: "schema", state: inspection.state === "unavailable" ? "unavailable" : match ? "matching" : "changed", differences: match ? [] : [{ attribute: "definition", expected: normalized, actual }] });
|
|
3782
3972
|
} else
|
|
3783
3973
|
objects.push({ identity, ownership: "schema", state: "unavailable", reason: "explicit-replacement-required", differences: [{ attribute: "definition", expected: expression, actual: stored }] });
|
|
3784
3974
|
}
|
|
@@ -3800,14 +3990,14 @@ async function verifyMigrationTransition(params) {
|
|
|
3800
3990
|
}
|
|
3801
3991
|
async function verifySqliteTransitionBeforeRestore(params) {
|
|
3802
3992
|
if (params.dialect !== "sqlite")
|
|
3803
|
-
throw new
|
|
3993
|
+
throw new VibeError15({ code: "VIBE_INTERNAL", message: "SQLite rebuild verification requires the SQLite dialect" });
|
|
3804
3994
|
await executeStatement({ executor: params.executor, dialect: "sqlite", statement: "PRAGMA foreign_key_check;" });
|
|
3805
3995
|
return verifyTransition({ ...params, sqliteRestorePending: true });
|
|
3806
3996
|
}
|
|
3807
3997
|
async function verifyTransition(params) {
|
|
3808
3998
|
const report = await verifyState({ executor: params.executor, dialect: params.dialect, expected: params.to, ...params.sqliteRestorePending === true ? { sqliteRestorePending: true } : {}, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
3809
3999
|
const namespace = await migrationNamespace(params);
|
|
3810
|
-
const identities = (
|
|
4000
|
+
const identities = (state) => [...schemaObjectIdentities({ schema: state.schema, dialect: params.dialect, namespace }), ...(state.schema.migrationObjects ?? []).map((object) => object.identity), ...state.definitions.map((definition) => definition.identity)];
|
|
3811
4001
|
const retained = new Set(identities(params.to).map((identity) => sqlObjectKey4({ identity })));
|
|
3812
4002
|
const removed = new Map([...identities(params.from).filter((identity) => !retained.has(sqlObjectKey4({ identity }))), ...params.removals ?? []].map((identity) => [sqlObjectKey4({ identity }), identity]));
|
|
3813
4003
|
const objects = [...report.objects];
|
|
@@ -3826,12 +4016,12 @@ async function migrationNamespace(params) {
|
|
|
3826
4016
|
const rows = await params.executor({ text: query.text, values: [] });
|
|
3827
4017
|
const namespace = rows[0]?.namespace;
|
|
3828
4018
|
if (typeof namespace !== "string" || namespace.length === 0)
|
|
3829
|
-
throw new
|
|
4019
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Cannot determine the migration namespace", meta: { reason: "inspection-unavailable" } });
|
|
3830
4020
|
return namespace;
|
|
3831
4021
|
}
|
|
3832
4022
|
function assertMigrationState(params) {
|
|
3833
4023
|
if (!params.report.inSync)
|
|
3834
|
-
throw new
|
|
4024
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Migration state could not be verified", meta: { reason: "migration-state-unverified", shadowReusable: true, objects: params.report.objects.filter((object) => object.state !== "matching") } });
|
|
3835
4025
|
}
|
|
3836
4026
|
async function deparsePostgresCheck(params) {
|
|
3837
4027
|
const definition = await deparsePostgresCheckDefinition({ ...params, constraint: "vibeorm_check_probe" });
|
|
@@ -3845,7 +4035,7 @@ async function deparsePostgresCheckDefinition(params) {
|
|
|
3845
4035
|
await params.executor({ text: query.text });
|
|
3846
4036
|
const result = await inspectSqlObject({ executor: params.executor, dialect: "postgres", identity: { kind: "constraint", constraintType: "check", table: params.table, name } });
|
|
3847
4037
|
if (result.state !== "present" || typeof result.definition.facts.definition !== "string")
|
|
3848
|
-
throw new
|
|
4038
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "CHECK normalization probe unavailable", meta: { reason: "check-inspection-unavailable" } });
|
|
3849
4039
|
return { ...result.definition, identity: { kind: "constraint", constraintType: "check", table: params.table, name: params.constraint }, facts: { ...result.definition.facts, definition: result.definition.facts.definition.replace(/ NOT VALID$/, ""), validated: true } };
|
|
3850
4040
|
} finally {
|
|
3851
4041
|
await params.executor({ text: "ROLLBACK TO SAVEPOINT vibeorm_check_probe" });
|
|
@@ -3863,7 +4053,7 @@ async function deparseMysqlCheck(params) {
|
|
|
3863
4053
|
const source = await params.executor({ text: read.text, values: [] });
|
|
3864
4054
|
const definition = source[0]?.["Create Table"];
|
|
3865
4055
|
if (typeof definition !== "string")
|
|
3866
|
-
throw new
|
|
4056
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Cannot read temporary clone definition" });
|
|
3867
4057
|
const drop = renderMysqlCheckProbe({ dialect, probe, operation: "drop" });
|
|
3868
4058
|
await params.executor({ text: drop.text });
|
|
3869
4059
|
created = false;
|
|
@@ -3874,7 +4064,7 @@ async function deparseMysqlCheck(params) {
|
|
|
3874
4064
|
const normalizedDefinition = rows[0]?.["Create Table"];
|
|
3875
4065
|
const check = typeof normalizedDefinition === "string" ? parseSqliteNamedChecks({ tableSql: normalizedDefinition }).find((entry) => entry.name === probe) : undefined;
|
|
3876
4066
|
if (check === undefined)
|
|
3877
|
-
throw new
|
|
4067
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "MySQL temporary CHECK deparse unavailable", meta: { reason: "check-inspection-unavailable" } });
|
|
3878
4068
|
return check.expression;
|
|
3879
4069
|
} finally {
|
|
3880
4070
|
if (created) {
|
|
@@ -3901,7 +4091,7 @@ async function prepareCheckDiff(params) {
|
|
|
3901
4091
|
const desired = await deparsePostgresCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "rollback" });
|
|
3902
4092
|
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3903
4093
|
if (live.state !== "present")
|
|
3904
|
-
throw new
|
|
4094
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
|
|
3905
4095
|
if (live.definition.facts.definition === desired) {
|
|
3906
4096
|
checks.push(next);
|
|
3907
4097
|
continue;
|
|
@@ -3913,7 +4103,7 @@ async function prepareCheckDiff(params) {
|
|
|
3913
4103
|
const desired = await deparseMysqlCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "temporary" });
|
|
3914
4104
|
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
3915
4105
|
if (live.state !== "present")
|
|
3916
|
-
throw new
|
|
4106
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
|
|
3917
4107
|
checks.push(desired === live.definition.facts.definition ? next : { name: check.name, expression: check.expression });
|
|
3918
4108
|
continue;
|
|
3919
4109
|
}
|
|
@@ -3926,7 +4116,7 @@ async function prepareCheckDiff(params) {
|
|
|
3926
4116
|
checks.push({ name: check.name, expression: check.expression });
|
|
3927
4117
|
continue;
|
|
3928
4118
|
}
|
|
3929
|
-
throw new
|
|
4119
|
+
throw new VibeError15({ code: "VIBE_MIGRATION", message: `Cannot establish CHECK equivalence for ${table}.${check.name}; request a rollback probe or an explicit replacement`, meta: { reason: "explicit-replacement-required", identity } });
|
|
3930
4120
|
}
|
|
3931
4121
|
models.push({ ...model, checks });
|
|
3932
4122
|
}
|
|
@@ -3953,7 +4143,7 @@ async function inspectMigrationInventory(params) {
|
|
|
3953
4143
|
for (const row of rows) {
|
|
3954
4144
|
const name = row.name;
|
|
3955
4145
|
if (typeof name !== "string" || typeof row.kind !== "string")
|
|
3956
|
-
throw new
|
|
4146
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Malformed catalog identity", meta: { reason: "inventory-unavailable" } });
|
|
3957
4147
|
const qualified = { namespace: params.namespace, name };
|
|
3958
4148
|
const table = { namespace: params.namespace, name: String(row.parent ?? "") };
|
|
3959
4149
|
let identity;
|
|
@@ -3975,27 +4165,27 @@ async function inspectMigrationInventory(params) {
|
|
|
3975
4165
|
case "constraint": {
|
|
3976
4166
|
const constraintType = kinds[String(row.subtype)];
|
|
3977
4167
|
if (constraintType === undefined)
|
|
3978
|
-
throw new
|
|
4168
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Unknown constraint catalog kind", meta: { reason: "inventory-unavailable" } });
|
|
3979
4169
|
identity = { kind: "constraint", table, name, constraintType };
|
|
3980
4170
|
break;
|
|
3981
4171
|
}
|
|
3982
4172
|
case "function": {
|
|
3983
4173
|
let argumentTypes = JSON.parse(String(row.signature));
|
|
3984
4174
|
if (params.dialect === "mysql") {
|
|
3985
|
-
const
|
|
3986
|
-
const members = await params.executor({ text:
|
|
4175
|
+
const query = renderMysqlObjectMembers2({ dialect: getSqlDialect2({ dialect: params.dialect }), identity: { kind: "function", name: qualified, argumentTypes: [] } });
|
|
4176
|
+
const members = await params.executor({ text: query.text, values: [...query.values] });
|
|
3987
4177
|
if (members.length === 0)
|
|
3988
|
-
throw new
|
|
4178
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "MySQL function signature unavailable", meta: { reason: "inventory-unavailable", name } });
|
|
3989
4179
|
argumentTypes = members.filter((member) => member.mode !== null).map((member) => ({ namespace: "mysql", name: String(member.type) }));
|
|
3990
4180
|
}
|
|
3991
4181
|
const candidate = { kind: "function", name: qualified, argumentTypes };
|
|
3992
4182
|
if (!isSqlObjectIdentity2(candidate))
|
|
3993
|
-
throw new
|
|
4183
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Invalid function signature in catalog", meta: { reason: "inventory-unavailable" } });
|
|
3994
4184
|
identity = candidate;
|
|
3995
4185
|
break;
|
|
3996
4186
|
}
|
|
3997
4187
|
default:
|
|
3998
|
-
throw new
|
|
4188
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: `Unknown catalog object kind ${row.kind}`, meta: { reason: "inventory-unavailable" } });
|
|
3999
4189
|
}
|
|
4000
4190
|
objects.push({ identity, ...typeof row.backing === "string" ? { backingConstraint: row.backing } : {} });
|
|
4001
4191
|
if (params.dialect === "sqlite" && row.kind === "table" && typeof row.definition === "string" && !/^\s*CREATE\s+VIRTUAL\s+TABLE\b/i.test(row.definition)) {
|
|
@@ -4003,18 +4193,18 @@ async function inspectMigrationInventory(params) {
|
|
|
4003
4193
|
for (const constraint of clauses) {
|
|
4004
4194
|
if (constraint.type === "check")
|
|
4005
4195
|
continue;
|
|
4006
|
-
const
|
|
4007
|
-
objects.push({ identity: { kind: "constraint", constraintType: constraint.type, table: qualified, name
|
|
4196
|
+
const name = constraint.named ? constraint.name : constraint.type === "primaryKey" ? defaultPrimaryKeyName2({ table: qualified.name, dialect: "sqlite" }) : constraint.type === "foreignKey" ? defaultForeignKeyName2({ table: qualified.name, columns: constraint.columns, dialect: "sqlite" }) : constraint.name;
|
|
4197
|
+
objects.push({ identity: { kind: "constraint", constraintType: constraint.type, table: qualified, name }, ...constraint.type === "unique" && !constraint.named ? { inspectionUnavailable: "sqlite-anonymous-unique-constraint" } : {} });
|
|
4008
4198
|
}
|
|
4009
4199
|
const checks = parseSqliteNamedChecks({ tableSql: row.definition });
|
|
4010
4200
|
for (const check of checks)
|
|
4011
4201
|
objects.push({ identity: { kind: "constraint", constraintType: "check", table: qualified, name: check.name } });
|
|
4012
4202
|
if ([...row.definition.matchAll(/\bCHECK\s*\(/gi)].length > checks.length)
|
|
4013
4203
|
objects.push({ identity: { kind: "table", name: qualified }, inspectionUnavailable: "sqlite-unnamed-check-definition-unavailable" });
|
|
4014
|
-
const
|
|
4015
|
-
const columns = await params.executor({ text:
|
|
4204
|
+
const query = renderSqliteColumnInventory2({ dialect: getSqlDialect2({ dialect: params.dialect }), namespace: params.namespace, table: name });
|
|
4205
|
+
const columns = await params.executor({ text: query.text, values: [...query.values] });
|
|
4016
4206
|
if (columns.some((column) => Number(column.pk) > 0) && !clauses.some((clause) => clause.type === "primaryKey"))
|
|
4017
|
-
objects.push({ identity: { kind: "constraint", constraintType: "primaryKey", table: qualified, name:
|
|
4207
|
+
objects.push({ identity: { kind: "constraint", constraintType: "primaryKey", table: qualified, name: defaultPrimaryKeyName2({ table: qualified.name, dialect: "sqlite" }) } });
|
|
4018
4208
|
const fkQuery = renderSqliteForeignKeyInventory({ dialect: getSqlDialect2({ dialect: params.dialect }), namespace: params.namespace, table: name });
|
|
4019
4209
|
const foreignKeys = await params.executor({ text: fkQuery.text, values: [...fkQuery.values] });
|
|
4020
4210
|
const groups = new Map;
|
|
@@ -4023,7 +4213,7 @@ async function inspectMigrationInventory(params) {
|
|
|
4023
4213
|
for (const fields of groups.values()) {
|
|
4024
4214
|
if (clauses.some((clause) => clause.type === "foreignKey" && JSON.stringify(clause.columns) === JSON.stringify(fields)))
|
|
4025
4215
|
continue;
|
|
4026
|
-
objects.push({ identity: { kind: "constraint", constraintType: "foreignKey", table: qualified, name:
|
|
4216
|
+
objects.push({ identity: { kind: "constraint", constraintType: "foreignKey", table: qualified, name: defaultForeignKeyName2({ table: qualified.name, columns: fields, dialect: "sqlite" }) }, inspectionUnavailable: "sqlite-inline-foreign-key-definition-unavailable" });
|
|
4027
4217
|
}
|
|
4028
4218
|
for (const column of columns) {
|
|
4029
4219
|
objects.push({ identity: { kind: "column", table: qualified, column: String(column.name) } });
|
|
@@ -4039,7 +4229,7 @@ async function inspectMigrationInventory(params) {
|
|
|
4039
4229
|
|
|
4040
4230
|
// packages/migrate/src/module-ownership.ts
|
|
4041
4231
|
function refusal(params) {
|
|
4042
|
-
return new
|
|
4232
|
+
return new VibeError17({ code: "VIBE_MIGRATION", message: "Module ownership or dependencies cannot be reconciled with the declared and live schema; no package SQL was authorized", meta: params });
|
|
4043
4233
|
}
|
|
4044
4234
|
function identities(params) {
|
|
4045
4235
|
return [...schemaObjectIdentities({ schema: params.state.schema, dialect: params.dialect, namespace: params.namespace }), ...(params.state.schema.migrationObjects ?? []).map((object) => object.identity), ...params.state.definitions.map((definition) => definition.identity)];
|
|
@@ -4055,14 +4245,14 @@ function moduleNamespace(params) {
|
|
|
4055
4245
|
function moduleOwnedObjects(params) {
|
|
4056
4246
|
const { desiredState } = params;
|
|
4057
4247
|
const namespace = params.namespace ?? (desiredState.dialect === "sqlite" ? "main" : "public");
|
|
4058
|
-
const all = [desiredState.from, desiredState.to, ...desiredState.down === undefined ? [] : [desiredState.down]].flatMap((
|
|
4248
|
+
const all = [desiredState.from, desiredState.to, ...desiredState.down === undefined ? [] : [desiredState.down]].flatMap((state) => identities({ state, dialect: desiredState.dialect, namespace }));
|
|
4059
4249
|
return [...new Map(all.map((identity) => [sqlObjectKey6({ identity }), identity])).values()].sort((a, b) => sqlObjectKey6({ identity: a }).localeCompare(sqlObjectKey6({ identity: b })));
|
|
4060
4250
|
}
|
|
4061
4251
|
function assertModuleOwnership(params) {
|
|
4062
4252
|
const { origin } = params;
|
|
4063
|
-
const
|
|
4253
|
+
const state = origin.source.desiredState;
|
|
4064
4254
|
const namespace = moduleNamespace({ origin });
|
|
4065
|
-
const expected = moduleOwnedObjects({ desiredState:
|
|
4255
|
+
const expected = moduleOwnedObjects({ desiredState: state, namespace });
|
|
4066
4256
|
const owned = new Set(origin.owned.map((identity) => sqlObjectKey6({ identity })));
|
|
4067
4257
|
if (expected.length !== owned.size || expected.some((identity) => !owned.has(sqlObjectKey6({ identity }))))
|
|
4068
4258
|
throw refusal({ reason: "module-ownership-invalid", moduleId: origin.moduleId });
|
|
@@ -4071,10 +4261,10 @@ function assertModuleOwnership(params) {
|
|
|
4071
4261
|
if (name?.namespace !== namespace || name.name === "_vibe_migrations")
|
|
4072
4262
|
throw refusal({ reason: "module-ownership-invalid", moduleId: origin.moduleId, identity });
|
|
4073
4263
|
}
|
|
4074
|
-
for (const target of [
|
|
4264
|
+
for (const target of [state.from, state.to, ...state.down === undefined ? [] : [state.down]]) {
|
|
4075
4265
|
if (target.schema.rls !== undefined || target.schema.extensions.length > 0)
|
|
4076
4266
|
throw refusal({ reason: "module-schema-unsupported", moduleId: origin.moduleId });
|
|
4077
|
-
for (const dependency of [...target.definitions.flatMap((definition) => definition.dependencies), ...(target.schema.migrationObjects ?? []).flatMap((object) => object.dependsOn), ...
|
|
4267
|
+
for (const dependency of [...target.definitions.flatMap((definition) => definition.dependencies), ...(target.schema.migrationObjects ?? []).flatMap((object) => object.dependsOn), ...state.unmanagedRemovals ?? []]) {
|
|
4078
4268
|
if (!owned.has(sqlObjectKey6({ identity: dependency })))
|
|
4079
4269
|
throw refusal({ reason: "module-external-dependency", moduleId: origin.moduleId, identity: dependency });
|
|
4080
4270
|
}
|
|
@@ -4095,10 +4285,39 @@ function assertModuleOwnership(params) {
|
|
|
4095
4285
|
throw refusal({ reason: "module-source-state-mismatch", moduleId: origin.moduleId });
|
|
4096
4286
|
return { ...actual, schema: { ...actual.schema, models: actual.schema.models.filter((model) => !tables.has(model.dbName ?? model.name)), enums: actual.schema.enums.filter((entry) => !enums.has(entry.dbName ?? entry.name)), migrationObjects: (actual.schema.migrationObjects ?? []).filter((object) => !owned.has(sqlObjectKey6({ identity: object.identity }))) }, definitions: actual.definitions.filter((definition) => !owned.has(sqlObjectKey6({ identity: definition.identity }))) };
|
|
4097
4287
|
};
|
|
4098
|
-
const before = canonicalMigrationJson({ value: rest(local.from,
|
|
4099
|
-
if (before !== canonicalMigrationJson({ value: rest(local.to,
|
|
4288
|
+
const before = canonicalMigrationJson({ value: rest(local.from, state.from) });
|
|
4289
|
+
if (before !== canonicalMigrationJson({ value: rest(local.to, state.to) }) || before !== canonicalMigrationJson({ value: rest(local.down ?? local.from, state.down ?? state.from) }))
|
|
4100
4290
|
throw refusal({ reason: "module-mutates-application-state", moduleId: origin.moduleId });
|
|
4101
4291
|
}
|
|
4292
|
+
function dependencyUnavailable(params) {
|
|
4293
|
+
return new VibeError17({ code: "VIBE_MIGRATION", message: "Cannot establish module foreign-key dependencies across namespaces. MySQL requires direct global SHOW VIEW (or SELECT) without partial revokes; SHOW VIEW exposes metadata without granting external row reads. Restore catalog visibility before retrying", meta: { reason: "module-dependency-unavailable", moduleId: params.origin.moduleId }, ...params.cause === undefined ? {} : { cause: params.cause } });
|
|
4294
|
+
}
|
|
4295
|
+
async function assertCrossNamespaceModuleDependencies(params) {
|
|
4296
|
+
const { executor, origin } = params;
|
|
4297
|
+
const dialect = getSqlDialect3({ dialect: origin.dialect });
|
|
4298
|
+
const query = renderModuleDependencyQuery({ dialect, operation: { kind: "foreignKeys", namespace: params.namespace, tables: [...params.tables] } });
|
|
4299
|
+
if (query === undefined)
|
|
4300
|
+
return;
|
|
4301
|
+
const visibility = renderModuleDependencyQuery({ dialect, operation: { kind: "visibility" } });
|
|
4302
|
+
if (visibility !== undefined) {
|
|
4303
|
+
const rows = await executor({ text: visibility.text, values: [...visibility.values] }).then(undefined, (cause) => {
|
|
4304
|
+
throw dependencyUnavailable({ origin, cause });
|
|
4305
|
+
});
|
|
4306
|
+
if (!hasMysqlGlobalCatalogVisibility({ rows }))
|
|
4307
|
+
throw dependencyUnavailable({ origin });
|
|
4308
|
+
}
|
|
4309
|
+
const rows = await executor({ text: query.text, values: [...query.values] }).then(undefined, (cause) => {
|
|
4310
|
+
throw dependencyUnavailable({ origin, cause });
|
|
4311
|
+
});
|
|
4312
|
+
const flags = [true, false, 0, 1, "0", "1"];
|
|
4313
|
+
for (const row of rows) {
|
|
4314
|
+
const { constraint_name: name, source_namespace: namespace, source_table: table, target_namespace: targetNamespace, target_table: targetTable, source_owned: source, target_owned: target } = row;
|
|
4315
|
+
if (typeof name !== "string" || name.length === 0 || typeof namespace !== "string" || namespace.length === 0 || typeof table !== "string" || table.length === 0 || typeof targetNamespace !== "string" || targetNamespace.length === 0 || typeof targetTable !== "string" || targetTable.length === 0 || !flags.includes(source) || !flags.includes(target) || Number(source) + Number(target) === 0)
|
|
4316
|
+
throw dependencyUnavailable({ origin });
|
|
4317
|
+
if (Number(source) !== Number(target))
|
|
4318
|
+
throw refusal({ reason: "module-external-dependency", moduleId: origin.moduleId, identity: { kind: "constraint", constraintType: "foreignKey", table: { namespace, name: table }, name } });
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4102
4321
|
async function assertLiveModuleOwnership(params) {
|
|
4103
4322
|
const { executor, origin } = params;
|
|
4104
4323
|
const namespace = moduleNamespace({ origin });
|
|
@@ -4108,6 +4327,7 @@ async function assertLiveModuleOwnership(params) {
|
|
|
4108
4327
|
const before = new Set(identities({ state: origin.source.desiredState.from, dialect: origin.dialect, namespace }).map((identity) => sqlObjectKey6({ identity })));
|
|
4109
4328
|
const owned = new Set(origin.owned.map((identity) => sqlObjectKey6({ identity })));
|
|
4110
4329
|
const tables = new Set(origin.owned.flatMap((identity) => identity.kind === "table" ? [identity.name.name] : []));
|
|
4330
|
+
await assertCrossNamespaceModuleDependencies({ executor, origin, namespace, tables });
|
|
4111
4331
|
for (const object of inventory) {
|
|
4112
4332
|
const key = sqlObjectKey6({ identity: object.identity });
|
|
4113
4333
|
if (params.resume !== true && owned.has(key) && !before.has(key))
|
|
@@ -4117,7 +4337,7 @@ async function assertLiveModuleOwnership(params) {
|
|
|
4117
4337
|
if (object.inspectionUnavailable !== undefined && (owned.has(key) || ("table" in object.identity) && tables.has(object.identity.table.name)))
|
|
4118
4338
|
throw refusal({ reason: "module-dependency-unavailable", moduleId: origin.moduleId, identity: object.identity });
|
|
4119
4339
|
}
|
|
4120
|
-
const physical = origin.dialect === "postgres" ? await introspectPhysicalPostgres({ executor }) : origin.dialect === "mysql" ? await introspectMysql({ executor }) : await introspectSqlite({ executor });
|
|
4340
|
+
const physical = origin.dialect === "postgres" ? await introspectPhysicalPostgres({ executor }) : origin.dialect === "mysql" ? await introspectMysql({ executor }) : params.sqliteRestorePending === true ? await introspectSqliteBeforeForeignKeyRestore({ executor }) : await introspectSqlite({ executor });
|
|
4121
4341
|
for (const model of physical.models)
|
|
4122
4342
|
for (const relation of model.relations) {
|
|
4123
4343
|
const target = physical.models.find((candidate) => candidate.name === relation.targetModel);
|
|
@@ -4125,13 +4345,23 @@ async function assertLiveModuleOwnership(params) {
|
|
|
4125
4345
|
throw refusal({ reason: "module-external-dependency", moduleId: origin.moduleId });
|
|
4126
4346
|
}
|
|
4127
4347
|
}
|
|
4348
|
+
async function assertApplicationModuleState(params) {
|
|
4349
|
+
for (const origin of params.origins) {
|
|
4350
|
+
await assertLiveModuleOwnership({ executor: params.executor, origin, resume: true, ...params.sqliteRestorePending === true ? { sqliteRestorePending: true } : {} });
|
|
4351
|
+
const state = origin.source.desiredState;
|
|
4352
|
+
const options = { executor: params.executor, dialect: origin.dialect, from: state.from, to: state.to, removals: state.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } };
|
|
4353
|
+
const report = params.sqliteRestorePending === true ? await verifySqliteTransitionBeforeRestore(options) : await verifyMigrationTransition(options);
|
|
4354
|
+
if (!report.inSync)
|
|
4355
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: `Application SQL cannot change objects owned by module ${origin.moduleId}; use its reviewed module upgrade`, meta: { reason: "module-application-mutation", moduleId: origin.moduleId, report } });
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4128
4358
|
|
|
4129
4359
|
// packages/migrate/src/sql-statements.ts
|
|
4130
|
-
import { VibeError as
|
|
4360
|
+
import { VibeError as VibeError18 } from "@vibeorm/schema";
|
|
4131
4361
|
var COMPOUND_BODY_HEADER = /^(?:\s|--[^\n]*\n|\/\*[\s\S]*?\*\/)*CREATE\s+(?:OR\s+REPLACE\s+)?(?:(?:TEMP|TEMPORARY)\s+)?(?:DEFINER\s*=\s*\S+\s+)?(?:TRIGGER|PROCEDURE|FUNCTION|EVENT)\b/i;
|
|
4132
4362
|
var BLOCK_OPENERS = new Set(["BEGIN", "CASE", "IF", "LOOP", "WHILE", "REPEAT"]);
|
|
4133
4363
|
function splitSqlStatements(params) {
|
|
4134
|
-
const { sql
|
|
4364
|
+
const { sql, dialect } = params;
|
|
4135
4365
|
const statements = [];
|
|
4136
4366
|
let current = "";
|
|
4137
4367
|
let hasContent = false;
|
|
@@ -4149,65 +4379,65 @@ function splitSqlStatements(params) {
|
|
|
4149
4379
|
compound = false;
|
|
4150
4380
|
afterEnd = false;
|
|
4151
4381
|
};
|
|
4152
|
-
while (i <
|
|
4153
|
-
const ch =
|
|
4154
|
-
const next =
|
|
4155
|
-
if (ch === "-" && next === "-" && (dialect !== "mysql" || /\s/.test(
|
|
4382
|
+
while (i < sql.length) {
|
|
4383
|
+
const ch = sql[i];
|
|
4384
|
+
const next = sql[i + 1];
|
|
4385
|
+
if (ch === "-" && next === "-" && (dialect !== "mysql" || /\s/.test(sql[i + 2] ?? `
|
|
4156
4386
|
`)) || ch === "#" && dialect === "mysql") {
|
|
4157
|
-
while (i <
|
|
4387
|
+
while (i < sql.length && sql[i] !== `
|
|
4158
4388
|
`) {
|
|
4159
|
-
current +=
|
|
4389
|
+
current += sql[i];
|
|
4160
4390
|
i++;
|
|
4161
4391
|
}
|
|
4162
4392
|
continue;
|
|
4163
4393
|
}
|
|
4164
4394
|
if (ch === "/" && next === "*") {
|
|
4165
|
-
current +=
|
|
4395
|
+
current += sql.slice(i, i + 2);
|
|
4166
4396
|
i += 2;
|
|
4167
4397
|
let depth = 1;
|
|
4168
|
-
while (i <
|
|
4169
|
-
if (
|
|
4398
|
+
while (i < sql.length && depth > 0) {
|
|
4399
|
+
if (sql[i] === "/" && sql[i + 1] === "*" && (dialect === undefined || dialect === "postgres")) {
|
|
4170
4400
|
depth++;
|
|
4171
4401
|
current += "/*";
|
|
4172
4402
|
i += 2;
|
|
4173
|
-
} else if (
|
|
4403
|
+
} else if (sql[i] === "*" && sql[i + 1] === "/") {
|
|
4174
4404
|
depth--;
|
|
4175
4405
|
current += "*/";
|
|
4176
4406
|
i += 2;
|
|
4177
4407
|
} else {
|
|
4178
|
-
current +=
|
|
4408
|
+
current += sql[i];
|
|
4179
4409
|
i++;
|
|
4180
4410
|
}
|
|
4181
4411
|
}
|
|
4182
4412
|
continue;
|
|
4183
4413
|
}
|
|
4184
|
-
const escapeString = (dialect === undefined || dialect === "postgres") && (ch === "e" || ch === "E") && next === "'" && !/[A-Za-z0-9_$]/.test(
|
|
4414
|
+
const escapeString = (dialect === undefined || dialect === "postgres") && (ch === "e" || ch === "E") && next === "'" && !/[A-Za-z0-9_$]/.test(sql[i - 1] ?? "");
|
|
4185
4415
|
if (escapeString) {
|
|
4186
4416
|
current += ch;
|
|
4187
4417
|
i++;
|
|
4188
4418
|
}
|
|
4189
|
-
const quote =
|
|
4419
|
+
const quote = sql[i];
|
|
4190
4420
|
if (escapeString || quote === "'" || quote === '"' || quote === "`" && dialect !== "postgres" || quote === "[" && (dialect === undefined || dialect === "sqlite")) {
|
|
4191
4421
|
hasContent = true;
|
|
4192
4422
|
const endQuote = quote === "[" ? "]" : quote;
|
|
4193
4423
|
current += quote;
|
|
4194
4424
|
i++;
|
|
4195
|
-
while (i <
|
|
4196
|
-
const value =
|
|
4425
|
+
while (i < sql.length) {
|
|
4426
|
+
const value = sql[i];
|
|
4197
4427
|
current += value;
|
|
4198
4428
|
if (value === "\\" && (quote === "'" || quote === '"')) {
|
|
4199
4429
|
if (escapeString) {
|
|
4200
|
-
if (
|
|
4201
|
-
current +=
|
|
4430
|
+
if (sql[i + 1] !== undefined)
|
|
4431
|
+
current += sql[i + 1];
|
|
4202
4432
|
i += 2;
|
|
4203
4433
|
continue;
|
|
4204
4434
|
}
|
|
4205
4435
|
if (dialect === "mysql" || dialect === undefined) {
|
|
4206
|
-
throw new
|
|
4436
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "SQL string backslash escaping depends on the session SQL mode; use mode-independent quoting and pass the migration dialect", meta: { reason: "sql-mode-ambiguous", dialect } });
|
|
4207
4437
|
}
|
|
4208
4438
|
}
|
|
4209
4439
|
if (value === endQuote) {
|
|
4210
|
-
if (quote !== "[" &&
|
|
4440
|
+
if (quote !== "[" && sql[i + 1] === endQuote) {
|
|
4211
4441
|
current += endQuote;
|
|
4212
4442
|
i += 2;
|
|
4213
4443
|
continue;
|
|
@@ -4219,14 +4449,14 @@ function splitSqlStatements(params) {
|
|
|
4219
4449
|
}
|
|
4220
4450
|
continue;
|
|
4221
4451
|
}
|
|
4222
|
-
if (ch === "$" && (dialect === undefined || dialect === "postgres") && !/[A-Za-z0-9_$]/.test(
|
|
4223
|
-
const tagMatch = /^\$(?:[A-Za-z_][A-Za-z_0-9]*)?\$/.exec(
|
|
4452
|
+
if (ch === "$" && (dialect === undefined || dialect === "postgres") && !/[A-Za-z0-9_$]/.test(sql[i - 1] ?? "")) {
|
|
4453
|
+
const tagMatch = /^\$(?:[A-Za-z_][A-Za-z_0-9]*)?\$/.exec(sql.slice(i));
|
|
4224
4454
|
if (tagMatch !== null) {
|
|
4225
4455
|
hasContent = true;
|
|
4226
4456
|
const tag = tagMatch[0];
|
|
4227
|
-
const end =
|
|
4228
|
-
const stop = end === -1 ?
|
|
4229
|
-
current +=
|
|
4457
|
+
const end = sql.indexOf(tag, i + tag.length);
|
|
4458
|
+
const stop = end === -1 ? sql.length : end + tag.length;
|
|
4459
|
+
current += sql.slice(i, stop);
|
|
4230
4460
|
i = stop;
|
|
4231
4461
|
continue;
|
|
4232
4462
|
}
|
|
@@ -4241,14 +4471,14 @@ function splitSqlStatements(params) {
|
|
|
4241
4471
|
i++;
|
|
4242
4472
|
continue;
|
|
4243
4473
|
}
|
|
4244
|
-
const word = /^[A-Za-z_][A-Za-z_0-9]*/.exec(
|
|
4474
|
+
const word = /^[A-Za-z_][A-Za-z_0-9]*/.exec(sql.slice(i))?.[0];
|
|
4245
4475
|
if (word !== undefined) {
|
|
4246
4476
|
hasContent = true;
|
|
4247
4477
|
if (!compound && COMPOUND_BODY_HEADER.test(current + word))
|
|
4248
4478
|
compound = true;
|
|
4249
4479
|
if (compound) {
|
|
4250
4480
|
const upper = word.toUpperCase();
|
|
4251
|
-
const rest =
|
|
4481
|
+
const rest = sql.slice(i + word.length);
|
|
4252
4482
|
const opens = !afterEnd && (upper === "BEGIN" || blockDepth > 0 && BLOCK_OPENERS.has(upper) && !/^\s*\(/.test(rest) && !(upper === "IF" && /^\s+(?:NOT\s+)?EXISTS\b/i.test(rest)));
|
|
4253
4483
|
afterEnd = upper === "END";
|
|
4254
4484
|
if (opens)
|
|
@@ -4277,10 +4507,10 @@ function joinSqlStatements(params) {
|
|
|
4277
4507
|
|
|
4278
4508
|
// packages/migrate/src/module-origin.ts
|
|
4279
4509
|
import { createHash } from "crypto";
|
|
4280
|
-
import { VibeError as
|
|
4510
|
+
import { VibeError as VibeError19, isSqlObjectIdentity as isSqlObjectIdentity3, sqlObjectKey as sqlObjectKey7 } from "@vibeorm/schema";
|
|
4281
4511
|
var MODULE_ORIGIN_METADATA = Object.freeze({ filename: "module-origin.json", version: 1 });
|
|
4282
4512
|
function invalid2() {
|
|
4283
|
-
return new
|
|
4513
|
+
return new VibeError19({ code: "VIBE_MIGRATION", message: "Module migration origin is malformed or differs from its immutable package artifact; restore the reviewed artifact", meta: { reason: "module-origin-invalid" } });
|
|
4284
4514
|
}
|
|
4285
4515
|
function record2(value) {
|
|
4286
4516
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -4344,7 +4574,7 @@ function assertMaterializedModuleMigration(params) {
|
|
|
4344
4574
|
return;
|
|
4345
4575
|
const restored = restoreModuleMigration({ migration, origin: migration.moduleOrigin });
|
|
4346
4576
|
if (migration.desiredState === undefined || canonicalMigrationJson({ value: restored }) !== canonicalMigrationJson({ value: migration }))
|
|
4347
|
-
throw new
|
|
4577
|
+
throw new VibeError19({ code: "VIBE_MIGRATION", message: "Materialized module metadata differs from its immutable source", meta: { reason: "module-source-state-mismatch", moduleId: migration.moduleOrigin.moduleId } });
|
|
4348
4578
|
}
|
|
4349
4579
|
function restoreModuleMigration(params) {
|
|
4350
4580
|
const { migration, origin } = params;
|
|
@@ -4353,7 +4583,7 @@ function restoreModuleMigration(params) {
|
|
|
4353
4583
|
const actual = migration[key];
|
|
4354
4584
|
if (actual === undefined)
|
|
4355
4585
|
continue;
|
|
4356
|
-
const normalized = (
|
|
4586
|
+
const normalized = (sql) => sql.flatMap((text) => splitSqlStatements({ sql: text, dialect: origin.dialect }));
|
|
4357
4587
|
if (canonicalMigrationJson({ value: normalized(actual) }) !== canonicalMigrationJson({ value: normalized(origin.source[key] ?? []) }))
|
|
4358
4588
|
throw invalid2();
|
|
4359
4589
|
}
|
|
@@ -4367,13 +4597,13 @@ function restoreModuleMigration(params) {
|
|
|
4367
4597
|
// packages/migrate/src/fs.ts
|
|
4368
4598
|
import { readFile, readdir } from "fs/promises";
|
|
4369
4599
|
import { join } from "path";
|
|
4370
|
-
import { VibeError as
|
|
4600
|
+
import { VibeError as VibeError33, assertValidSchemaIR as assertValidSchemaIR4 } from "@vibeorm/schema";
|
|
4371
4601
|
|
|
4372
4602
|
// packages/migrate/src/migration-checksum.ts
|
|
4373
4603
|
import { createHash as createHash2 } from "crypto";
|
|
4374
4604
|
|
|
4375
4605
|
// packages/migrate/src/execution.ts
|
|
4376
|
-
import { VibeError as
|
|
4606
|
+
import { VibeError as VibeError20 } from "@vibeorm/schema";
|
|
4377
4607
|
var EXECUTION_SIDECAR_FILENAME = "execution.json";
|
|
4378
4608
|
var EXECUTION_SIDECAR_VERSION = 1;
|
|
4379
4609
|
function isDefaultExecution(params) {
|
|
@@ -4412,7 +4642,7 @@ ${lines.join(`
|
|
|
4412
4642
|
`)}`;
|
|
4413
4643
|
}
|
|
4414
4644
|
function invalid3(params) {
|
|
4415
|
-
return new
|
|
4645
|
+
return new VibeError20({
|
|
4416
4646
|
code: "VIBE_MIGRATION",
|
|
4417
4647
|
message: `invalid execution metadata for migration "${params.migration}": ${params.detail}`,
|
|
4418
4648
|
meta: { reason: "execution-metadata-invalid", migration: params.migration }
|
|
@@ -4463,7 +4693,7 @@ function parseExecutionSidecar(params) {
|
|
|
4463
4693
|
try {
|
|
4464
4694
|
parsed = JSON.parse(params.text);
|
|
4465
4695
|
} catch (error) {
|
|
4466
|
-
throw new
|
|
4696
|
+
throw new VibeError20({
|
|
4467
4697
|
code: "VIBE_MIGRATION",
|
|
4468
4698
|
message: `invalid execution metadata for migration "${migration}": not valid JSON`,
|
|
4469
4699
|
meta: { reason: "execution-metadata-invalid", migration },
|
|
@@ -4561,16 +4791,35 @@ function migrationChecksum(params) {
|
|
|
4561
4791
|
-- vibeorm:down ${JSON.stringify(params.downSql ?? null)}${executionChecksumSuffix({ ...params.downExecution === undefined ? {} : { execution: params.downExecution } })}`)).digest("hex");
|
|
4562
4792
|
}
|
|
4563
4793
|
|
|
4794
|
+
// packages/migrate/src/shadow-guard.ts
|
|
4795
|
+
import { VibeError as VibeError21 } from "@vibeorm/schema";
|
|
4796
|
+
import { getSqlDialect as getSqlDialect4, renderShadowOperation } from "@vibeorm/sql";
|
|
4797
|
+
var replayExecutors = new WeakSet;
|
|
4798
|
+
async function assertDatabaseNotShadow(params) {
|
|
4799
|
+
if (params.dialect === "sqlite" || replayExecutors.has(params.executor))
|
|
4800
|
+
return;
|
|
4801
|
+
const [query] = renderShadowOperation({ dialect: getSqlDialect4({ dialect: params.dialect }), operation: { kind: "markerExists" } });
|
|
4802
|
+
const rows = await params.executor({ text: query.text, values: [...query.values] });
|
|
4803
|
+
if (rows.length !== 0)
|
|
4804
|
+
throw new VibeError21({ code: "VIBE_MIGRATION", message: "This database is claimed as a disposable migration shadow. Application migrations must use the deployment target.", meta: { reason: "shadow-database-is-not-target" } });
|
|
4805
|
+
}
|
|
4806
|
+
function runShadowReplay(params) {
|
|
4807
|
+
replayExecutors.add(params.executor);
|
|
4808
|
+
return Promise.resolve().then(params.run).finally(() => {
|
|
4809
|
+
replayExecutors.delete(params.executor);
|
|
4810
|
+
});
|
|
4811
|
+
}
|
|
4812
|
+
|
|
4564
4813
|
// packages/migrate/src/module-history.ts
|
|
4565
|
-
import { VibeError as
|
|
4814
|
+
import { VibeError as VibeError22, sqlObjectKey as sqlObjectKey8 } from "@vibeorm/schema";
|
|
4566
4815
|
function refusal2(params) {
|
|
4567
|
-
return new
|
|
4816
|
+
return new VibeError22({ code: "VIBE_MIGRATION", message: `Module ${params.moduleId} history, ownership or predecessor state is inconsistent; restore or complete the reviewed upgrade first`, meta: params });
|
|
4568
4817
|
}
|
|
4569
4818
|
function moduleHistoryHeads(params) {
|
|
4570
4819
|
const heads = new Map;
|
|
4571
4820
|
const revisions = new Map;
|
|
4572
|
-
for (const
|
|
4573
|
-
const origin =
|
|
4821
|
+
for (const record of params.records.values()) {
|
|
4822
|
+
const origin = record.entry?.migration.moduleOrigin;
|
|
4574
4823
|
if (origin === undefined)
|
|
4575
4824
|
continue;
|
|
4576
4825
|
const previous = heads.get(origin.moduleId);
|
|
@@ -4583,10 +4832,21 @@ function moduleHistoryHeads(params) {
|
|
|
4583
4832
|
throw refusal2({ reason: "module-predecessor-state", moduleId: origin.moduleId });
|
|
4584
4833
|
seen.add(origin.revision);
|
|
4585
4834
|
revisions.set(origin.moduleId, seen);
|
|
4586
|
-
heads.set(origin.moduleId,
|
|
4835
|
+
heads.set(origin.moduleId, record);
|
|
4587
4836
|
}
|
|
4588
4837
|
return heads;
|
|
4589
4838
|
}
|
|
4839
|
+
function applicationModuleOrigins(params) {
|
|
4840
|
+
if (params.migration.moduleOrigin !== undefined || params.migration.desiredState !== undefined)
|
|
4841
|
+
return [];
|
|
4842
|
+
const origins = [];
|
|
4843
|
+
for (const [moduleId, head] of moduleHistoryHeads(params)) {
|
|
4844
|
+
if (!head.entry.complete || head.row.error !== undefined || head.row.statementIndex !== head.entry.migration.sql.length)
|
|
4845
|
+
throw refusal2({ reason: "module-chain-incomplete", moduleId });
|
|
4846
|
+
origins.push(head.entry.migration.moduleOrigin);
|
|
4847
|
+
}
|
|
4848
|
+
return origins;
|
|
4849
|
+
}
|
|
4590
4850
|
function preserves(params) {
|
|
4591
4851
|
const tables = new Set(params.expected.schema.models.map((model) => model.dbName ?? model.name));
|
|
4592
4852
|
const enums = new Set(params.expected.schema.enums.map((entry) => entry.dbName ?? entry.name));
|
|
@@ -4617,9 +4877,9 @@ function assertModuleUpgradeHistory(params) {
|
|
|
4617
4877
|
owners.set(key, origin.moduleId);
|
|
4618
4878
|
}
|
|
4619
4879
|
};
|
|
4620
|
-
for (const
|
|
4621
|
-
if (
|
|
4622
|
-
register(
|
|
4880
|
+
for (const record of params.records.values())
|
|
4881
|
+
if (record.entry !== undefined)
|
|
4882
|
+
register(record.entry.migration);
|
|
4623
4883
|
for (const migration of params.migrations) {
|
|
4624
4884
|
const stored = params.records.get(migration.name);
|
|
4625
4885
|
const origin = migration.moduleOrigin;
|
|
@@ -4631,7 +4891,7 @@ function assertModuleUpgradeHistory(params) {
|
|
|
4631
4891
|
if (origin === undefined) {
|
|
4632
4892
|
for (const [moduleId, head] of heads) {
|
|
4633
4893
|
if (migration.desiredState === undefined)
|
|
4634
|
-
|
|
4894
|
+
continue;
|
|
4635
4895
|
const expected = head.entry.migration.moduleOrigin.source.desiredState.to;
|
|
4636
4896
|
for (const actual of [migration.desiredState.from, migration.desiredState.to, migration.desiredState.down ?? migration.desiredState.from])
|
|
4637
4897
|
preserves({ actual, expected, moduleId });
|
|
@@ -4658,22 +4918,22 @@ function assertModuleUpgradeHistory(params) {
|
|
|
4658
4918
|
}
|
|
4659
4919
|
|
|
4660
4920
|
// packages/migrate/src/module-journal.ts
|
|
4661
|
-
import { VibeError as
|
|
4662
|
-
import { getSqlDialect as
|
|
4921
|
+
import { VibeError as VibeError24 } from "@vibeorm/schema";
|
|
4922
|
+
import { getSqlDialect as getSqlDialect5, MODULE_JOURNAL_COLUMN, renderModuleJournal } from "@vibeorm/sql";
|
|
4663
4923
|
|
|
4664
4924
|
// packages/migrate/src/journal-read.ts
|
|
4665
|
-
import { VibeError as
|
|
4925
|
+
import { VibeError as VibeError23 } from "@vibeorm/schema";
|
|
4666
4926
|
var MISSING_TABLE_IDENTIFIERS = new Set(["42P01", "42S02", "ER_NO_SUCH_TABLE"]);
|
|
4667
4927
|
function isMissingTable(params) {
|
|
4668
4928
|
let current = params.cause;
|
|
4669
4929
|
for (let depth = 0;depth < 6 && current !== null && typeof current === "object"; depth++) {
|
|
4670
|
-
const
|
|
4930
|
+
const record = current;
|
|
4671
4931
|
for (const key of ["code", "errno", "sqlState"]) {
|
|
4672
|
-
const value =
|
|
4932
|
+
const value = record[key];
|
|
4673
4933
|
if (typeof value === "string" && MISSING_TABLE_IDENTIFIERS.has(value))
|
|
4674
4934
|
return true;
|
|
4675
4935
|
}
|
|
4676
|
-
current =
|
|
4936
|
+
current = record.cause;
|
|
4677
4937
|
}
|
|
4678
4938
|
return false;
|
|
4679
4939
|
}
|
|
@@ -4686,7 +4946,7 @@ async function readJournalRows(params) {
|
|
|
4686
4946
|
if (presence.state === "missing")
|
|
4687
4947
|
return;
|
|
4688
4948
|
}
|
|
4689
|
-
throw new
|
|
4949
|
+
throw new VibeError23({ code: "VIBE_MIGRATION", message: "Cannot inspect migration history; check SELECT privileges", meta: { reason: "history-unavailable" }, cause });
|
|
4690
4950
|
});
|
|
4691
4951
|
if (rows === undefined)
|
|
4692
4952
|
return;
|
|
@@ -4694,7 +4954,7 @@ async function readJournalRows(params) {
|
|
|
4694
4954
|
for (const row of rows) {
|
|
4695
4955
|
const index = row.statement_index;
|
|
4696
4956
|
if (typeof row.name !== "string" || typeof row.checksum !== "string" || typeof index !== "number" && typeof index !== "string" || typeof index === "string" && !/^-?\d+$/.test(index) || !Number.isSafeInteger(Number(index)) || row.module_origin !== null && row.module_origin !== undefined && typeof row.module_origin !== "string" || row.error !== null && row.error !== undefined && typeof row.error !== "string" || result.has(row.name)) {
|
|
4697
|
-
throw new
|
|
4957
|
+
throw new VibeError23({ code: "VIBE_MIGRATION", message: "Malformed migration journal row", meta: { reason: "history-unavailable" } });
|
|
4698
4958
|
}
|
|
4699
4959
|
result.set(row.name, { checksum: row.checksum, statementIndex: Number(index), ...typeof row.error === "string" ? { error: row.error } : {}, ...typeof row.module_origin === "string" ? { moduleEntry: row.module_origin } : {} });
|
|
4700
4960
|
}
|
|
@@ -4703,7 +4963,7 @@ async function readJournalRows(params) {
|
|
|
4703
4963
|
|
|
4704
4964
|
// packages/migrate/src/module-journal.ts
|
|
4705
4965
|
function invalid4() {
|
|
4706
|
-
return new
|
|
4966
|
+
return new VibeError24({ code: "VIBE_MIGRATION", message: "Module migration history is missing, altered or inconsistent; restore the reviewed history before using this module", meta: { reason: "module-history-invalid" } });
|
|
4707
4967
|
}
|
|
4708
4968
|
function parseModuleJournalEntry(params) {
|
|
4709
4969
|
return parseMigrationMetadata({ text: params.text, failure: invalid4, parse: (value) => {
|
|
@@ -4719,7 +4979,7 @@ function parseModuleJournalEntry(params) {
|
|
|
4719
4979
|
} });
|
|
4720
4980
|
}
|
|
4721
4981
|
async function readModuleJournal(params) {
|
|
4722
|
-
const query = renderModuleJournal({ dialect:
|
|
4982
|
+
const query = renderModuleJournal({ dialect: getSqlDialect5({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: { kind: "read" } });
|
|
4723
4983
|
const rows = await readJournalRows({ ...params, query });
|
|
4724
4984
|
return new Map([...rows ?? []].map(([name, row]) => {
|
|
4725
4985
|
const entry = row.moduleEntry === undefined ? undefined : parseModuleJournalEntry({ text: row.moduleEntry, name, checksum: row.checksum });
|
|
@@ -4729,14 +4989,14 @@ async function readModuleJournal(params) {
|
|
|
4729
4989
|
}));
|
|
4730
4990
|
}
|
|
4731
4991
|
async function ensureModuleJournal(params) {
|
|
4732
|
-
const dialect =
|
|
4992
|
+
const dialect = getSqlDialect5({ dialect: params.dialect });
|
|
4733
4993
|
const columns = await params.executor(renderModuleJournal({ dialect, table: MIGRATIONS_TABLE_NAME, operation: { kind: "columns" } }));
|
|
4734
4994
|
if (columns.some((row) => row.name === MODULE_JOURNAL_COLUMN))
|
|
4735
4995
|
return;
|
|
4736
4996
|
await params.executor(renderModuleJournal({ dialect, table: MIGRATIONS_TABLE_NAME, operation: { kind: "addColumn" } }));
|
|
4737
4997
|
}
|
|
4738
4998
|
async function insertModuleJournal(params) {
|
|
4739
|
-
await params.executor(renderModuleJournal({ dialect:
|
|
4999
|
+
await params.executor(renderModuleJournal({ dialect: getSqlDialect5({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: {
|
|
4740
5000
|
kind: "insert",
|
|
4741
5001
|
name: params.migration.name,
|
|
4742
5002
|
checksum: migrationChecksum(params.migration),
|
|
@@ -4745,7 +5005,7 @@ async function insertModuleJournal(params) {
|
|
|
4745
5005
|
} }));
|
|
4746
5006
|
}
|
|
4747
5007
|
async function completeModuleJournal(params) {
|
|
4748
|
-
await params.executor(renderModuleJournal({ dialect:
|
|
5008
|
+
await params.executor(renderModuleJournal({ dialect: getSqlDialect5({ dialect: params.dialect }), table: MIGRATIONS_TABLE_NAME, operation: {
|
|
4749
5009
|
kind: "complete",
|
|
4750
5010
|
name: params.migration.name,
|
|
4751
5011
|
entry: canonicalMigrationJson({ value: { version: 1, migration: params.migration, complete: true } })
|
|
@@ -4762,19 +5022,19 @@ function moduleRecordComplete(params) {
|
|
|
4762
5022
|
}
|
|
4763
5023
|
|
|
4764
5024
|
// packages/migrate/src/runner.ts
|
|
4765
|
-
import { VibeError as
|
|
5025
|
+
import { VibeError as VibeError32 } from "@vibeorm/schema";
|
|
4766
5026
|
import {
|
|
4767
5027
|
DIALECT_CAPABILITIES as DIALECT_CAPABILITIES3,
|
|
4768
|
-
getSqlDialect as
|
|
5028
|
+
getSqlDialect as getSqlDialect7,
|
|
4769
5029
|
renderMigrationSessionRead,
|
|
4770
5030
|
renderMigrationSessionRestore,
|
|
4771
5031
|
renderMigrationSessionSet
|
|
4772
5032
|
} from "@vibeorm/sql";
|
|
4773
5033
|
|
|
4774
5034
|
// packages/migrate/src/recovery.ts
|
|
4775
|
-
import { VibeError as
|
|
5035
|
+
import { VibeError as VibeError25 } from "@vibeorm/schema";
|
|
4776
5036
|
import {
|
|
4777
|
-
getSqlDialect as
|
|
5037
|
+
getSqlDialect as getSqlDialect6,
|
|
4778
5038
|
renderColumnStateQuery,
|
|
4779
5039
|
renderConstraintStateQuery,
|
|
4780
5040
|
renderIndexStateQuery,
|
|
@@ -4788,7 +5048,7 @@ function constraintUnverifiable(params) {
|
|
|
4788
5048
|
});
|
|
4789
5049
|
}
|
|
4790
5050
|
function conflict2(params) {
|
|
4791
|
-
return new
|
|
5051
|
+
return new VibeError25({
|
|
4792
5052
|
code: "VIBE_MIGRATION",
|
|
4793
5053
|
message: params.message,
|
|
4794
5054
|
meta: { reason: params.reason, ...params.meta }
|
|
@@ -4813,7 +5073,7 @@ function sameShape(params) {
|
|
|
4813
5073
|
}
|
|
4814
5074
|
async function establishOperationState(params) {
|
|
4815
5075
|
const { executor, operation } = params;
|
|
4816
|
-
const dialect =
|
|
5076
|
+
const dialect = getSqlDialect6({ dialect: params.dialect });
|
|
4817
5077
|
const run = async (query) => executor({ text: query.text, values: [...query.values] });
|
|
4818
5078
|
switch (operation.kind) {
|
|
4819
5079
|
case "createIndexConcurrently": {
|
|
@@ -4893,10 +5153,10 @@ async function establishOperationState(params) {
|
|
|
4893
5153
|
|
|
4894
5154
|
// packages/migrate/src/differ.ts
|
|
4895
5155
|
import { isEnumType as isEnumType6, unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
|
|
4896
|
-
import { VibeError as
|
|
5156
|
+
import { VibeError as VibeError27 } from "@vibeorm/schema";
|
|
4897
5157
|
|
|
4898
5158
|
// packages/migrate/src/renames.ts
|
|
4899
|
-
import { VibeError as
|
|
5159
|
+
import { VibeError as VibeError26 } from "@vibeorm/schema";
|
|
4900
5160
|
function tableOf(model) {
|
|
4901
5161
|
return model.dbName ?? model.name;
|
|
4902
5162
|
}
|
|
@@ -4907,7 +5167,7 @@ function hasColumn(params) {
|
|
|
4907
5167
|
return params.model.fields.some((field) => (field.dbName ?? field.name) === params.column);
|
|
4908
5168
|
}
|
|
4909
5169
|
function ambiguous(params) {
|
|
4910
|
-
return new
|
|
5170
|
+
return new VibeError26({
|
|
4911
5171
|
code: "VIBE_MIGRATION",
|
|
4912
5172
|
message: `rename refused: ${params.detail}`,
|
|
4913
5173
|
meta: { reason: "rename-ambiguous", ...params.meta }
|
|
@@ -5055,7 +5315,7 @@ function diffSchemas(params) {
|
|
|
5055
5315
|
if (before?.definition === exclusion.definition)
|
|
5056
5316
|
continue;
|
|
5057
5317
|
if (before?.catalogDefinition)
|
|
5058
|
-
throw new
|
|
5318
|
+
throw new VibeError27({ code: "VIBE_MIGRATION", message: "Exclusion comparison requires normalized evidence", meta: { reason: "exclusion-inspection-unavailable", table, name: exclusion.name } });
|
|
5059
5319
|
if (before !== undefined)
|
|
5060
5320
|
buckets.dropExclusion.push({ kind: "dropExclusion", table, exclusion: before, destructive: false });
|
|
5061
5321
|
buckets.addExclusion.push({ kind: "addExclusion", table, exclusion, destructive: false });
|
|
@@ -5267,7 +5527,7 @@ function diffOneTable(params) {
|
|
|
5267
5527
|
changes.push("collation");
|
|
5268
5528
|
if (changes.length === 0)
|
|
5269
5529
|
continue;
|
|
5270
|
-
if (changes.includes("type") && [...previous.checks].some(([
|
|
5530
|
+
if (changes.includes("type") && [...previous.checks].some(([name, expression]) => current.checks.get(name) === expression)) {
|
|
5271
5531
|
throw unsupportedCapability6({ dialect, feature: "alterColumnWithRetainedCheck", hint: "CHECK dependencies and type compatibility are not proven; use a reviewed migration that replaces and revalidates the affected constraints" });
|
|
5272
5532
|
}
|
|
5273
5533
|
const storageChange = isEnumStringTransition({ from: before.field, to: column.field });
|
|
@@ -5331,7 +5591,7 @@ function diffOneTable(params) {
|
|
|
5331
5591
|
if (before === expression)
|
|
5332
5592
|
continue;
|
|
5333
5593
|
if (before !== undefined && previous.model.checks?.some((check) => check.name === name && check.catalogExpression === true))
|
|
5334
|
-
throw new
|
|
5594
|
+
throw new VibeError27({ code: "VIBE_MIGRATION", message: "Unequal catalog CHECK text requires normalized evidence or explicit replacement through the live planner", meta: { reason: "check-inspection-unavailable", table, name } });
|
|
5335
5595
|
if (before !== undefined)
|
|
5336
5596
|
buckets.dropCheck.push({ kind: "dropCheck", table, name, destructive: false });
|
|
5337
5597
|
buckets.addCheck.push({ kind: "addCheck", table, name, expression, destructive: false });
|
|
@@ -5410,12 +5670,12 @@ function diffOneTable(params) {
|
|
|
5410
5670
|
}
|
|
5411
5671
|
|
|
5412
5672
|
// packages/migrate/src/ddl/render.ts
|
|
5413
|
-
import { VibeError as
|
|
5673
|
+
import { VibeError as VibeError31, sqlObjectKey as sqlObjectKey9 } from "@vibeorm/schema";
|
|
5414
5674
|
|
|
5415
5675
|
// packages/migrate/src/ddl/postgres.ts
|
|
5416
5676
|
import { renderExclusionSetup } from "@vibeorm/sql";
|
|
5417
|
-
import { VibeError as
|
|
5418
|
-
import { postgresDialect as
|
|
5677
|
+
import { VibeError as VibeError28, defaultPrimaryKeyName as defaultPrimaryKeyName3, implicitJoinTableObjectNames as implicitJoinTableObjectNames2, isEnumType as isEnumType7 } from "@vibeorm/schema";
|
|
5678
|
+
import { postgresDialect as postgresDialect8, renderRlsPolicyPath } from "@vibeorm/sql";
|
|
5419
5679
|
function renderPostgresSteps(params) {
|
|
5420
5680
|
refuseSameTransactionEnumUse({ steps: params.steps });
|
|
5421
5681
|
return params.steps.flatMap((step) => renderStep({ step }));
|
|
@@ -5449,7 +5709,7 @@ function refuseSameTransactionEnumUse(params) {
|
|
|
5449
5709
|
const found = use(field);
|
|
5450
5710
|
if (found === undefined)
|
|
5451
5711
|
continue;
|
|
5452
|
-
throw new
|
|
5712
|
+
throw new VibeError28({
|
|
5453
5713
|
code: "VIBE_MIGRATION",
|
|
5454
5714
|
message: `enum value "${found.value}" is added to "${found.enumName}" and used as a default on "${step.table}"."${field.dbName ?? field.name}" in the same migration \u2014 PostgreSQL cannot use an enum member before the transaction that added it commits. Split this into two migrations: the first containing only the ALTER TYPE ... ADD VALUE, the second the column change`,
|
|
5455
5715
|
meta: { enum: found.enumName, value: found.value, table: step.table, column: field.dbName ?? field.name, rule: "enum-value-used-in-adding-transaction" }
|
|
@@ -5461,7 +5721,7 @@ function renderStep(params) {
|
|
|
5461
5721
|
const { step } = params;
|
|
5462
5722
|
switch (step.kind) {
|
|
5463
5723
|
case "addExclusion":
|
|
5464
|
-
return [...renderExclusionSetup({ dialect:
|
|
5724
|
+
return [...renderExclusionSetup({ dialect: postgresDialect8, exclusion: step.exclusion }), `ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.exclusion.name)} ${step.exclusion.definition};`];
|
|
5465
5725
|
case "dropExclusion":
|
|
5466
5726
|
return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.exclusion.name)};`];
|
|
5467
5727
|
case "enableRls":
|
|
@@ -5470,7 +5730,7 @@ function renderStep(params) {
|
|
|
5470
5730
|
case "noForceRls":
|
|
5471
5731
|
case "createPolicy":
|
|
5472
5732
|
case "dropPolicy":
|
|
5473
|
-
return step.kind === "createPolicy" ? [renderRlsPolicyPath({ dialect:
|
|
5733
|
+
return step.kind === "createPolicy" ? [renderRlsPolicyPath({ dialect: postgresDialect8, stage: "save" }).text, renderRlsPolicyPath({ dialect: postgresDialect8, stage: "empty" }).text, ...renderRlsStep({ step, dialect: "postgres" }), renderRlsPolicyPath({ dialect: postgresDialect8, stage: "restore" }).text] : renderRlsStep({ step, dialect: "postgres" });
|
|
5474
5734
|
case "createEnum": {
|
|
5475
5735
|
const name = step.enumDef.dbName ?? step.enumDef.name;
|
|
5476
5736
|
const values = step.enumDef.values.map((value) => quotePgString({ value })).join(", ");
|
|
@@ -5486,7 +5746,7 @@ function renderStep(params) {
|
|
|
5486
5746
|
const lines = step.columns.map((column) => ` ${columnDefSql({ field: column })}`);
|
|
5487
5747
|
if (step.primaryKey.length > 0) {
|
|
5488
5748
|
const pkCols = step.primaryKey.map((column) => q2(column)).join(", ");
|
|
5489
|
-
lines.push(` CONSTRAINT ${q2(step.primaryKeyConstraintName ??
|
|
5749
|
+
lines.push(` CONSTRAINT ${q2(step.primaryKeyConstraintName ?? defaultPrimaryKeyName3({ table: step.table, dialect: "postgres" }))} PRIMARY KEY (${pkCols})`);
|
|
5490
5750
|
}
|
|
5491
5751
|
for (const check of step.checks ?? []) {
|
|
5492
5752
|
lines.push(` CONSTRAINT ${q2(check.name)} CHECK (${check.expression})`);
|
|
@@ -5520,7 +5780,7 @@ ${lines.join(`,
|
|
|
5520
5780
|
case "dropIndex":
|
|
5521
5781
|
if (step.constraintName !== undefined) {
|
|
5522
5782
|
if (step.table === undefined)
|
|
5523
|
-
throw new
|
|
5783
|
+
throw new VibeError28({ code: "VIBE_MIGRATION", message: "Constraint-backed index removal requires its owning table" });
|
|
5524
5784
|
return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.constraintName)};`];
|
|
5525
5785
|
}
|
|
5526
5786
|
return [`DROP INDEX ${publicName(step.name)};`];
|
|
@@ -5559,32 +5819,33 @@ ${lines.join(`,
|
|
|
5559
5819
|
case "createJoinTable": {
|
|
5560
5820
|
const aType = columnTypeSql({ field: { ...step.a.field, isList: false } });
|
|
5561
5821
|
const bType = columnTypeSql({ field: { ...step.b.field, isList: false } });
|
|
5822
|
+
const names = implicitJoinTableObjectNames2({ table: step.table });
|
|
5562
5823
|
return [
|
|
5563
5824
|
`CREATE TABLE ${publicName(step.table)} (
|
|
5564
5825
|
"A" ${aType} NOT NULL,
|
|
5565
5826
|
"B" ${bType} NOT NULL
|
|
5566
5827
|
);`,
|
|
5567
|
-
`CREATE UNIQUE INDEX ${q2(
|
|
5568
|
-
`CREATE INDEX ${q2(
|
|
5569
|
-
`ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(
|
|
5570
|
-
`ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(
|
|
5828
|
+
`CREATE UNIQUE INDEX ${q2(names.uniqueIndex)} ON ${publicName(step.table)} ("A", "B");`,
|
|
5829
|
+
`CREATE INDEX ${q2(names.indexB)} ON ${publicName(step.table)} ("B");`,
|
|
5830
|
+
`ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(names.foreignKeyA)} FOREIGN KEY ("A") REFERENCES ${publicName(step.a.table)} (${q2(step.a.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`,
|
|
5831
|
+
`ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(names.foreignKeyB)} FOREIGN KEY ("B") REFERENCES ${publicName(step.b.table)} (${q2(step.b.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`
|
|
5571
5832
|
];
|
|
5572
5833
|
}
|
|
5573
5834
|
case "addPrimaryKey": {
|
|
5574
5835
|
const cols = step.columns.map((column) => q2(column)).join(", ");
|
|
5575
5836
|
return [
|
|
5576
|
-
`ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.constraintName ??
|
|
5837
|
+
`ALTER TABLE ${publicName(step.table)} ADD CONSTRAINT ${q2(step.constraintName ?? defaultPrimaryKeyName3({ table: step.table, dialect: "postgres" }))} PRIMARY KEY (${cols});`
|
|
5577
5838
|
];
|
|
5578
5839
|
}
|
|
5579
5840
|
case "dropPrimaryKey":
|
|
5580
|
-
return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.constraintName ??
|
|
5841
|
+
return [`ALTER TABLE ${publicName(step.table)} DROP CONSTRAINT ${q2(step.constraintName ?? defaultPrimaryKeyName3({ table: step.table, dialect: "postgres" }))};`];
|
|
5581
5842
|
}
|
|
5582
5843
|
}
|
|
5583
5844
|
function columnDefSql(params) {
|
|
5584
5845
|
const { field } = params;
|
|
5585
5846
|
const name = q2(field.dbName ?? field.name);
|
|
5586
5847
|
if (field.default?.kind === "autoincrement" && !isIdentityColumn({ field })) {
|
|
5587
|
-
throw new
|
|
5848
|
+
throw new VibeError28({
|
|
5588
5849
|
code: "VIBE_MIGRATION",
|
|
5589
5850
|
message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
|
|
5590
5851
|
meta: { field: field.name }
|
|
@@ -5620,7 +5881,7 @@ function renderAlterColumn(params) {
|
|
|
5620
5881
|
}
|
|
5621
5882
|
if (step.changes.includes("default") || droppedDefault) {
|
|
5622
5883
|
if (step.to.default?.kind === "autoincrement") {
|
|
5623
|
-
throw new
|
|
5884
|
+
throw new VibeError28({
|
|
5624
5885
|
code: "VIBE_MIGRATION",
|
|
5625
5886
|
message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
|
|
5626
5887
|
meta: { table: step.table, column: step.column }
|
|
@@ -5635,11 +5896,11 @@ function publicName(name) {
|
|
|
5635
5896
|
return `${q2("public")}.${q2(name)}`;
|
|
5636
5897
|
}
|
|
5637
5898
|
function q2(name) {
|
|
5638
|
-
return
|
|
5899
|
+
return postgresDialect8.quoteIdent({ name });
|
|
5639
5900
|
}
|
|
5640
5901
|
|
|
5641
5902
|
// packages/migrate/src/ddl/sqlite.ts
|
|
5642
|
-
import { VibeError as
|
|
5903
|
+
import { VibeError as VibeError29, implicitJoinTableObjectNames as implicitJoinTableObjectNames3, isEnumType as isEnumType8, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
5643
5904
|
import { sqliteDialect as sqliteDialect3 } from "@vibeorm/sql";
|
|
5644
5905
|
|
|
5645
5906
|
// packages/migrate/src/ddl/drop-order.ts
|
|
@@ -5779,7 +6040,7 @@ function renderSqliteSteps(params) {
|
|
|
5779
6040
|
continue;
|
|
5780
6041
|
const awaitsNewParent = [...toNorm?.tables.get(table)?.fks.values() ?? []].some((fk) => createdTables.has(fk.refTable));
|
|
5781
6042
|
if (awaitsNewParent) {
|
|
5782
|
-
throw new
|
|
6043
|
+
throw new VibeError29({
|
|
5783
6044
|
code: "VIBE_MIGRATION",
|
|
5784
6045
|
message: `cannot order this sqlite migration safely: rebuilding "${table}" must happen BEFORE the dropped table(s) it references (${[
|
|
5785
6046
|
...droppedTables
|
|
@@ -5808,7 +6069,7 @@ function renderSqliteSteps(params) {
|
|
|
5808
6069
|
switch (step.kind) {
|
|
5809
6070
|
case "addExclusion":
|
|
5810
6071
|
case "dropExclusion":
|
|
5811
|
-
throw new
|
|
6072
|
+
throw new VibeError29({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
|
|
5812
6073
|
case "createEnum":
|
|
5813
6074
|
case "dropEnum":
|
|
5814
6075
|
case "addEnumValue":
|
|
@@ -5837,12 +6098,13 @@ function renderSqliteSteps(params) {
|
|
|
5837
6098
|
case "createJoinTable": {
|
|
5838
6099
|
const aType = sqliteStorageType({ field: { ...step.a.field, isList: false } });
|
|
5839
6100
|
const bType = sqliteStorageType({ field: { ...step.b.field, isList: false } });
|
|
6101
|
+
const names = implicitJoinTableObjectNames3({ table: step.table });
|
|
5840
6102
|
statements.push(`CREATE TABLE ${q3(step.table)} (
|
|
5841
6103
|
"A" ${aType} NOT NULL,
|
|
5842
6104
|
"B" ${bType} NOT NULL,
|
|
5843
|
-
CONSTRAINT ${q3(
|
|
5844
|
-
CONSTRAINT ${q3(
|
|
5845
|
-
);`, `CREATE UNIQUE INDEX ${q3(
|
|
6105
|
+
CONSTRAINT ${q3(names.foreignKeyA)} FOREIGN KEY ("A") REFERENCES ${q3(step.a.table)} (${q3(step.a.column)}) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
6106
|
+
CONSTRAINT ${q3(names.foreignKeyB)} FOREIGN KEY ("B") REFERENCES ${q3(step.b.table)} (${q3(step.b.column)}) ON DELETE CASCADE ON UPDATE CASCADE
|
|
6107
|
+
);`, `CREATE UNIQUE INDEX ${q3(names.uniqueIndex)} ON ${q3(step.table)} ("A", "B");`, `CREATE INDEX ${q3(names.indexB)} ON ${q3(step.table)} ("B");`);
|
|
5846
6108
|
break;
|
|
5847
6109
|
}
|
|
5848
6110
|
case "addColumn":
|
|
@@ -5893,7 +6155,7 @@ function renderSqliteSteps(params) {
|
|
|
5893
6155
|
case "renameColumn": {
|
|
5894
6156
|
const renamed = step.kind === "renameTable" ? step.from : step.table;
|
|
5895
6157
|
if (rebuildSet.has(renamed) || step.kind === "renameTable" && rebuildSet.has(step.to)) {
|
|
5896
|
-
throw new
|
|
6158
|
+
throw new VibeError29({
|
|
5897
6159
|
code: "VIBE_MIGRATION",
|
|
5898
6160
|
message: `sqlite cannot rename "${renamed}" in the same migration that rebuilds it \u2014 the table-rebuild recipe is written against a single table name. Split the rename and the column/constraint change into two migrations`,
|
|
5899
6161
|
meta: { reason: "rename-ambiguous", table: renamed, dialect: "sqlite" }
|
|
@@ -5947,7 +6209,7 @@ function classifyRebuilds(params) {
|
|
|
5947
6209
|
switch (step.kind) {
|
|
5948
6210
|
case "addExclusion":
|
|
5949
6211
|
case "dropExclusion":
|
|
5950
|
-
throw new
|
|
6212
|
+
throw new VibeError29({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
|
|
5951
6213
|
case "alterColumn":
|
|
5952
6214
|
case "dropForeignKey":
|
|
5953
6215
|
case "addPrimaryKey":
|
|
@@ -6008,7 +6270,7 @@ function renderTableRebuild(params) {
|
|
|
6008
6270
|
const source = from.tables.get(table);
|
|
6009
6271
|
const target = to.tables.get(table);
|
|
6010
6272
|
if (source === undefined || target === undefined) {
|
|
6011
|
-
throw new
|
|
6273
|
+
throw new VibeError29({
|
|
6012
6274
|
code: "VIBE_MIGRATION",
|
|
6013
6275
|
message: `cannot rebuild "${table}" \u2014 the table is missing from the ${source === undefined ? "from" : "to"} schema`,
|
|
6014
6276
|
meta: { table }
|
|
@@ -6143,7 +6405,7 @@ function columnDefSql2(params) {
|
|
|
6143
6405
|
const { field, constraintTable, enums, inlinePkAutoincrement } = params;
|
|
6144
6406
|
const name = field.dbName ?? field.name;
|
|
6145
6407
|
if (field.default?.kind === "autoincrement" && !inlinePkAutoincrement) {
|
|
6146
|
-
throw new
|
|
6408
|
+
throw new VibeError29({
|
|
6147
6409
|
code: "VIBE_MIGRATION",
|
|
6148
6410
|
message: `autoincrement on sqlite requires "${field.name}" to be the single-column Int or BigInt primary key`,
|
|
6149
6411
|
meta: { field: field.name }
|
|
@@ -6162,7 +6424,7 @@ function columnDefSql2(params) {
|
|
|
6162
6424
|
const enumName = field.type.enum;
|
|
6163
6425
|
const values = resolveEnumValues({ name: enumName, enums });
|
|
6164
6426
|
if (values === undefined) {
|
|
6165
|
-
throw new
|
|
6427
|
+
throw new VibeError29({
|
|
6166
6428
|
code: "VIBE_MIGRATION",
|
|
6167
6429
|
message: `enum "${enumName}" on column "${field.name}" is not declared in the schema \u2014 sqlite rendering needs the target schema's enums for the CHECK constraint`,
|
|
6168
6430
|
meta: { field: field.name, enum: enumName }
|
|
@@ -6197,7 +6459,7 @@ function requireSchemas(params) {
|
|
|
6197
6459
|
const { step, fromNorm, toNorm } = params;
|
|
6198
6460
|
if (fromNorm !== undefined && toNorm !== undefined)
|
|
6199
6461
|
return;
|
|
6200
|
-
throw new
|
|
6462
|
+
throw new VibeError29({
|
|
6201
6463
|
code: "VIBE_MIGRATION",
|
|
6202
6464
|
message: `sqlite step "${describeStep({ step })}" requires a table rebuild \u2014 renderSteps needs the { from, to } schemas to construct it`,
|
|
6203
6465
|
meta: { step: step.kind }
|
|
@@ -6208,7 +6470,7 @@ function q3(name) {
|
|
|
6208
6470
|
}
|
|
6209
6471
|
|
|
6210
6472
|
// packages/migrate/src/ddl/mysql.ts
|
|
6211
|
-
import { VibeError as
|
|
6473
|
+
import { VibeError as VibeError30, implicitJoinTableObjectNames as implicitJoinTableObjectNames4, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
6212
6474
|
import { mysqlDialect as mysqlDialect3 } from "@vibeorm/sql";
|
|
6213
6475
|
function renderMysqlSteps(params) {
|
|
6214
6476
|
for (const step of params.steps) {
|
|
@@ -6350,12 +6612,12 @@ ${lines.join(`,
|
|
|
6350
6612
|
];
|
|
6351
6613
|
case "addExclusion":
|
|
6352
6614
|
case "dropExclusion":
|
|
6353
|
-
throw new
|
|
6615
|
+
throw new VibeError30({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "mysql", feature: "exclusionConstraints" } });
|
|
6354
6616
|
case "dropCheck":
|
|
6355
6617
|
return [`ALTER TABLE ${q4(step.table)} DROP CHECK ${q4(step.name)};`];
|
|
6356
6618
|
case "dropIndex": {
|
|
6357
6619
|
if (step.table === undefined) {
|
|
6358
|
-
throw new
|
|
6620
|
+
throw new VibeError30({
|
|
6359
6621
|
code: "VIBE_MIGRATION",
|
|
6360
6622
|
message: `dropIndex "${step.name}" carries no table \u2014 mysql's DROP INDEX requires one`,
|
|
6361
6623
|
meta: { index: step.name }
|
|
@@ -6387,15 +6649,16 @@ ${lines.join(`,
|
|
|
6387
6649
|
case "createJoinTable": {
|
|
6388
6650
|
const aType = mysqlColumnTypeSql({ field: { ...step.a.field, isList: false }, enums });
|
|
6389
6651
|
const bType = mysqlColumnTypeSql({ field: { ...step.b.field, isList: false }, enums });
|
|
6652
|
+
const names = implicitJoinTableObjectNames4({ table: step.table });
|
|
6390
6653
|
return [
|
|
6391
6654
|
`CREATE TABLE ${q4(step.table)} (
|
|
6392
6655
|
\`A\` ${aType} NOT NULL,
|
|
6393
6656
|
\`B\` ${bType} NOT NULL
|
|
6394
6657
|
);`,
|
|
6395
|
-
`CREATE UNIQUE INDEX ${q4(
|
|
6396
|
-
`CREATE INDEX ${q4(
|
|
6397
|
-
`ALTER TABLE ${q4(step.table)} ADD CONSTRAINT ${q4(
|
|
6398
|
-
`ALTER TABLE ${q4(step.table)} ADD CONSTRAINT ${q4(
|
|
6658
|
+
`CREATE UNIQUE INDEX ${q4(names.uniqueIndex)} ON ${q4(step.table)} (\`A\`, \`B\`);`,
|
|
6659
|
+
`CREATE INDEX ${q4(names.indexB)} ON ${q4(step.table)} (\`B\`);`,
|
|
6660
|
+
`ALTER TABLE ${q4(step.table)} ADD CONSTRAINT ${q4(names.foreignKeyA)} FOREIGN KEY (\`A\`) REFERENCES ${q4(step.a.table)} (${q4(step.a.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`,
|
|
6661
|
+
`ALTER TABLE ${q4(step.table)} ADD CONSTRAINT ${q4(names.foreignKeyB)} FOREIGN KEY (\`B\`) REFERENCES ${q4(step.b.table)} (${q4(step.b.column)}) ON DELETE CASCADE ON UPDATE CASCADE;`
|
|
6399
6662
|
];
|
|
6400
6663
|
}
|
|
6401
6664
|
case "addPrimaryKey":
|
|
@@ -6411,7 +6674,7 @@ function columnDefSql3(params) {
|
|
|
6411
6674
|
const name = q4(field.dbName ?? field.name);
|
|
6412
6675
|
const autoincrement = field.default?.kind === "autoincrement";
|
|
6413
6676
|
if (autoincrement && field.type !== "Int" && field.type !== "BigInt") {
|
|
6414
|
-
throw new
|
|
6677
|
+
throw new VibeError30({
|
|
6415
6678
|
code: "VIBE_MIGRATION",
|
|
6416
6679
|
message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
|
|
6417
6680
|
meta: { field: field.name }
|
|
@@ -6433,7 +6696,7 @@ function renderAlterColumn2(params) {
|
|
|
6433
6696
|
const { step, enums } = params;
|
|
6434
6697
|
assertEnumStringTransition({ from: step.from, to: step.to, dialect: "mysql", table: step.table, column: step.column });
|
|
6435
6698
|
if (step.to.default?.kind === "autoincrement" && step.from.default?.kind !== "autoincrement") {
|
|
6436
|
-
throw new
|
|
6699
|
+
throw new VibeError30({
|
|
6437
6700
|
code: "VIBE_MIGRATION",
|
|
6438
6701
|
message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
|
|
6439
6702
|
meta: { table: step.table, column: step.column }
|
|
@@ -6465,32 +6728,32 @@ function declaredSqlitePreservation(params) {
|
|
|
6465
6728
|
let to = params.to;
|
|
6466
6729
|
if (params.from === undefined || to === undefined || (params.from.migrationObjects?.length ?? 0) === 0)
|
|
6467
6730
|
return { to, restore, columns };
|
|
6468
|
-
const
|
|
6731
|
+
const invalid = sqliteInvalidatedTables({ steps: params.steps, from: params.from });
|
|
6469
6732
|
const retained = new Set((to.migrationObjects ?? []).map((object) => sqlObjectKey9({ identity: object.identity })));
|
|
6470
6733
|
for (const object of params.from.migrationObjects ?? []) {
|
|
6471
6734
|
const identity = object.identity;
|
|
6472
|
-
if (!("table" in identity) || identity.table.namespace !== "main" || !
|
|
6735
|
+
if (!("table" in identity) || identity.table.namespace !== "main" || !invalid.rebuilt.has(identity.table.name) || !retained.has(sqlObjectKey9({ identity })))
|
|
6473
6736
|
continue;
|
|
6474
6737
|
const facts = object.expected?.facts;
|
|
6475
6738
|
if (facts === undefined)
|
|
6476
|
-
throw new
|
|
6739
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "SQLite rebuild requires independently declared preservation definitions", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
6477
6740
|
if (identity.kind === "trigger" || identity.kind === "index") {
|
|
6478
6741
|
if (identity.kind === "index" && to.models.some((model) => (model.dbName ?? model.name) === identity.table.name && model.indexes.some((index) => index.name === identity.name)))
|
|
6479
6742
|
continue;
|
|
6480
6743
|
if (typeof facts.definition !== "string")
|
|
6481
|
-
throw new
|
|
6744
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "Missing SQLite restoration SQL", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
6482
6745
|
const statements = restore.get(identity.table.name) ?? [];
|
|
6483
6746
|
statements.push(facts.definition);
|
|
6484
6747
|
restore.set(identity.table.name, statements);
|
|
6485
6748
|
} else if (identity.kind === "column" || identity.kind === "generatedColumn") {
|
|
6486
6749
|
if (typeof facts.columnDefinition !== "string")
|
|
6487
|
-
throw new
|
|
6750
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "Missing SQLite column clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
6488
6751
|
const fields = columns.get(identity.table.name) ?? new Map;
|
|
6489
6752
|
fields.set(identity.column, { sql: facts.columnDefinition, generated: Number(facts.generated) > 1 });
|
|
6490
6753
|
columns.set(identity.table.name, fields);
|
|
6491
6754
|
} else if (identity.kind === "columnDefault") {
|
|
6492
6755
|
if (facts.definition !== null && typeof facts.definition !== "string")
|
|
6493
|
-
throw new
|
|
6756
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "Missing SQLite default clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
6494
6757
|
const expression = facts.definition;
|
|
6495
6758
|
to = { ...to, models: to.models.map((model) => (model.dbName ?? model.name) !== identity.table.name ? model : { ...model, fields: model.fields.map((field) => {
|
|
6496
6759
|
if ((field.dbName ?? field.name) !== identity.column)
|
|
@@ -6634,19 +6897,19 @@ function planMissingArtifacts(params) {
|
|
|
6634
6897
|
}
|
|
6635
6898
|
async function probeInstalledExtensionState(params) {
|
|
6636
6899
|
const { executor, dialect, artifacts } = params;
|
|
6637
|
-
const
|
|
6900
|
+
const state = {};
|
|
6638
6901
|
if (dialect === "postgres" && artifacts.some((artifact) => artifact.owns.kind === "postgresExtension")) {
|
|
6639
6902
|
const rows = await executor({ text: "SELECT extname FROM pg_extension" });
|
|
6640
|
-
|
|
6903
|
+
state.pgExtensions = new Set(rows.map((row) => String(row["extname"])));
|
|
6641
6904
|
}
|
|
6642
6905
|
if (dialect === "sqlite" && artifacts.some((artifact) => artifact.owns.kind === "trigger")) {
|
|
6643
6906
|
const rows = await executor({
|
|
6644
6907
|
text: "SELECT name FROM sqlite_master WHERE type = 'trigger'",
|
|
6645
6908
|
values: []
|
|
6646
6909
|
});
|
|
6647
|
-
|
|
6910
|
+
state.triggers = new Set(rows.map((row) => String(row["name"])));
|
|
6648
6911
|
}
|
|
6649
|
-
return
|
|
6912
|
+
return state;
|
|
6650
6913
|
}
|
|
6651
6914
|
function artifactCreateSql(params) {
|
|
6652
6915
|
return params.artifacts.flatMap((artifact) => [...artifact.create]);
|
|
@@ -6659,29 +6922,29 @@ function migrationRlsSecurityChanges(params) {
|
|
|
6659
6922
|
return securityChangesOf({ steps: withRlsSteps({ from: params.down ? source.to : source.from, to: params.down ? source.from : source.to, steps: [] }) });
|
|
6660
6923
|
}
|
|
6661
6924
|
function containsNativeRlsSql(params) {
|
|
6662
|
-
const { sql
|
|
6925
|
+
const { sql, dialect } = params;
|
|
6663
6926
|
const tokens = [];
|
|
6664
6927
|
let i = 0;
|
|
6665
|
-
while (i <
|
|
6666
|
-
const ch =
|
|
6928
|
+
while (i < sql.length) {
|
|
6929
|
+
const ch = sql[i];
|
|
6667
6930
|
if (/\s/.test(ch)) {
|
|
6668
6931
|
i++;
|
|
6669
6932
|
continue;
|
|
6670
6933
|
}
|
|
6671
|
-
if (
|
|
6672
|
-
const end =
|
|
6934
|
+
if (sql.startsWith("--", i) || dialect === "mysql" && ch === "#") {
|
|
6935
|
+
const end = sql.indexOf(`
|
|
6673
6936
|
`, i);
|
|
6674
|
-
i = end === -1 ?
|
|
6937
|
+
i = end === -1 ? sql.length : end + 1;
|
|
6675
6938
|
continue;
|
|
6676
6939
|
}
|
|
6677
|
-
if (
|
|
6940
|
+
if (sql.startsWith("/*", i)) {
|
|
6678
6941
|
let depth = 1;
|
|
6679
6942
|
i += 2;
|
|
6680
|
-
while (i <
|
|
6681
|
-
if (
|
|
6943
|
+
while (i < sql.length && depth > 0) {
|
|
6944
|
+
if (sql.startsWith("/*", i)) {
|
|
6682
6945
|
depth++;
|
|
6683
6946
|
i += 2;
|
|
6684
|
-
} else if (
|
|
6947
|
+
} else if (sql.startsWith("*/", i)) {
|
|
6685
6948
|
depth--;
|
|
6686
6949
|
i += 2;
|
|
6687
6950
|
} else
|
|
@@ -6691,15 +6954,15 @@ function containsNativeRlsSql(params) {
|
|
|
6691
6954
|
}
|
|
6692
6955
|
if (ch === "'" || ch === '"' || ch === "`" || dialect === "sqlite" && ch === "[") {
|
|
6693
6956
|
const endQuote = ch === "[" ? "]" : ch;
|
|
6694
|
-
const backslash = dialect === "mysql" || ch === "'" && /(?:^|[^\w$])[eE]$/.test(
|
|
6957
|
+
const backslash = dialect === "mysql" || ch === "'" && /(?:^|[^\w$])[eE]$/.test(sql.slice(0, i));
|
|
6695
6958
|
i++;
|
|
6696
|
-
while (i <
|
|
6697
|
-
if (backslash &&
|
|
6959
|
+
while (i < sql.length) {
|
|
6960
|
+
if (backslash && sql[i] === "\\") {
|
|
6698
6961
|
i += 2;
|
|
6699
6962
|
continue;
|
|
6700
6963
|
}
|
|
6701
|
-
if (
|
|
6702
|
-
if (
|
|
6964
|
+
if (sql[i] === endQuote) {
|
|
6965
|
+
if (sql[i + 1] === endQuote) {
|
|
6703
6966
|
i += 2;
|
|
6704
6967
|
continue;
|
|
6705
6968
|
}
|
|
@@ -6712,15 +6975,15 @@ function containsNativeRlsSql(params) {
|
|
|
6712
6975
|
continue;
|
|
6713
6976
|
}
|
|
6714
6977
|
if (ch === "$") {
|
|
6715
|
-
const tag = /^(?:\$\$|\$[A-Za-z_][A-Za-z_0-9]*\$)/.exec(
|
|
6978
|
+
const tag = /^(?:\$\$|\$[A-Za-z_][A-Za-z_0-9]*\$)/.exec(sql.slice(i))?.[0];
|
|
6716
6979
|
if (tag !== undefined) {
|
|
6717
|
-
const end =
|
|
6718
|
-
i = end === -1 ?
|
|
6980
|
+
const end = sql.indexOf(tag, i + tag.length);
|
|
6981
|
+
i = end === -1 ? sql.length : end + tag.length;
|
|
6719
6982
|
tokens.push("@");
|
|
6720
6983
|
continue;
|
|
6721
6984
|
}
|
|
6722
6985
|
}
|
|
6723
|
-
const word = /^[A-Za-z_][A-Za-z_0-9$]*/.exec(
|
|
6986
|
+
const word = /^[A-Za-z_][A-Za-z_0-9$]*/.exec(sql.slice(i))?.[0];
|
|
6724
6987
|
tokens.push(word === undefined ? ch : word.toUpperCase());
|
|
6725
6988
|
i += word?.length ?? 1;
|
|
6726
6989
|
}
|
|
@@ -6730,14 +6993,14 @@ function containsNativeRlsSql(params) {
|
|
|
6730
6993
|
});
|
|
6731
6994
|
}
|
|
6732
6995
|
function assertNativeMigrationMetadata(params) {
|
|
6733
|
-
const native = params.migration.rls !== undefined || params.sql.some((
|
|
6996
|
+
const native = params.migration.rls !== undefined || params.sql.some((sql) => containsNativeRlsSql({ sql, dialect: params.dialect }));
|
|
6734
6997
|
if (!native)
|
|
6735
6998
|
return;
|
|
6736
6999
|
if (!DIALECT_CAPABILITIES3[params.dialect].nativeRowLevelSecurity) {
|
|
6737
|
-
throw new
|
|
7000
|
+
throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Native RLS migrations are unsupported on ${params.dialect}`, meta: { feature: "nativeRowLevelSecurity", dialect: params.dialect } });
|
|
6738
7001
|
}
|
|
6739
7002
|
if (params.migration.rls === undefined) {
|
|
6740
|
-
throw new
|
|
7003
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Native RLS migration files require trusted rls.from/rls.to schema metadata. SQL alone cannot prove policy ownership or drift; regenerate with preserved security metadata.", meta: { reason: "rls-migration-metadata-required", migration: params.migration.name } });
|
|
6741
7004
|
}
|
|
6742
7005
|
}
|
|
6743
7006
|
var PG_ADVISORY_LOCK_KEY = 1447645253;
|
|
@@ -6779,14 +7042,14 @@ function checksumOf(params) {
|
|
|
6779
7042
|
return migrationChecksum(migration);
|
|
6780
7043
|
}
|
|
6781
7044
|
async function assertForwardStart(params) {
|
|
6782
|
-
const
|
|
6783
|
-
if (
|
|
7045
|
+
const state = params.migration.desiredState;
|
|
7046
|
+
if (state === undefined)
|
|
6784
7047
|
return;
|
|
6785
|
-
const report = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected:
|
|
7048
|
+
const report = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected: state.from, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
6786
7049
|
if (report.inSync)
|
|
6787
7050
|
return;
|
|
6788
|
-
if (
|
|
6789
|
-
const restored = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected:
|
|
7051
|
+
if (state.down !== undefined) {
|
|
7052
|
+
const restored = await verifyMigrationState({ executor: params.executor, dialect: params.dialect, expected: state.down, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
6790
7053
|
if (restored.inSync)
|
|
6791
7054
|
return;
|
|
6792
7055
|
}
|
|
@@ -6806,7 +7069,7 @@ function generateMigration(params) {
|
|
|
6806
7069
|
const steps = diffSchemas({ from: strippedFrom, to, dialect, ...params.renames === undefined ? {} : { renames: params.renames } });
|
|
6807
7070
|
const coreSql = renderSteps({ steps, dialect, from: dialect === "sqlite" ? from : strippedFrom, to: dialect === "sqlite" ? sqlitePreservedSchema({ from, to, objects: [...from.migrationObjects ?? [], ...to.migrationObjects ?? []] }) : to });
|
|
6808
7071
|
const missing = planMissingArtifacts({ introspected: from, artifacts: allArtifacts });
|
|
6809
|
-
const
|
|
7072
|
+
const sql = [
|
|
6810
7073
|
...artifactCreateSql({
|
|
6811
7074
|
artifacts: missing.filter((artifact) => artifact.owns.kind === "postgresExtension")
|
|
6812
7075
|
}),
|
|
@@ -6816,18 +7079,18 @@ function generateMigration(params) {
|
|
|
6816
7079
|
}),
|
|
6817
7080
|
...orderedMigrationObjects({ objects: to.migrationObjects ?? [] }).flatMap((object) => {
|
|
6818
7081
|
if (object.dialect !== dialect)
|
|
6819
|
-
throw new
|
|
7082
|
+
throw new VibeError32({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Migration object ${object.id} requires ${object.dialect}` });
|
|
6820
7083
|
const previous = (params.desiredState?.from.schema.migrationObjects ?? from.migrationObjects)?.find((entry) => entry.id === object.id);
|
|
6821
7084
|
if (previous === undefined)
|
|
6822
7085
|
return [...object.create];
|
|
6823
7086
|
if (JSON.stringify(previous) !== JSON.stringify(object))
|
|
6824
|
-
throw new
|
|
7087
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration-owned object ${object.id} changed; supply a reviewed forward SQL migration`, meta: { reason: "migration-object-replacement-required", shadowReusable: true, id: object.id } });
|
|
6825
7088
|
return [];
|
|
6826
7089
|
})
|
|
6827
7090
|
];
|
|
6828
7091
|
return {
|
|
6829
7092
|
name,
|
|
6830
|
-
sql
|
|
7093
|
+
sql,
|
|
6831
7094
|
destructive: steps.some((step) => step.destructive),
|
|
6832
7095
|
...securityChangesOf({ steps }).length === 0 ? {} : { securityChanges: securityChangesOf({ steps }) },
|
|
6833
7096
|
...from.rls === undefined && to.rls === undefined ? {} : { rls: { from, to } },
|
|
@@ -6835,6 +7098,25 @@ function generateMigration(params) {
|
|
|
6835
7098
|
desiredState: params.desiredState ?? { version: 1, dialect, from: createDesiredState({ schema: from, dialect }), to: createDesiredState({ schema: to, dialect }) }
|
|
6836
7099
|
};
|
|
6837
7100
|
}
|
|
7101
|
+
async function withMigrationLock(params) {
|
|
7102
|
+
const runLocked = async (executor) => {
|
|
7103
|
+
await acquireLock({ executor, dialect: params.dialect });
|
|
7104
|
+
return Promise.resolve().then(async () => {
|
|
7105
|
+
await assertDatabaseNotShadow({ executor, dialect: params.dialect });
|
|
7106
|
+
return params.run(executor);
|
|
7107
|
+
}).then(async (value) => {
|
|
7108
|
+
await releaseLock({ executor, dialect: params.dialect });
|
|
7109
|
+
return value;
|
|
7110
|
+
}, (cause) => releaseLock({ executor, dialect: params.dialect }).then(() => {
|
|
7111
|
+
throw cause;
|
|
7112
|
+
}, (cleanupCause) => {
|
|
7113
|
+
if (cause instanceof VibeError32)
|
|
7114
|
+
throw new VibeError32({ code: cause.code, message: cause.message, meta: { ...cause.meta, cleanupCause }, cause });
|
|
7115
|
+
throw cause;
|
|
7116
|
+
}));
|
|
7117
|
+
};
|
|
7118
|
+
return params.executor.withSession === undefined ? runLocked(params.executor) : params.executor.withSession({ run: runLocked });
|
|
7119
|
+
}
|
|
6838
7120
|
function validateMigrationReplay(params) {
|
|
6839
7121
|
const dialect = params.dialect ?? "postgres";
|
|
6840
7122
|
assertProbeMode({ dialect, probe: params.probe });
|
|
@@ -6845,8 +7127,8 @@ function validateMigrationReplay(params) {
|
|
|
6845
7127
|
assertSupportedSelfManagedScript({ sql: migration.sql, migration: migration.name, direction: "up" });
|
|
6846
7128
|
assertNamedRemovals({ removals: migration.desiredState?.unmanagedRemovals ?? [], approved: params.removeUnmanaged, acceptIntegrityLoss: params.acceptIntegrityLoss });
|
|
6847
7129
|
if (migration.desiredState !== undefined) {
|
|
6848
|
-
const
|
|
6849
|
-
assertRemovalAuthorization({ steps: diffSchemas({ from:
|
|
7130
|
+
const state = migration.desiredState;
|
|
7131
|
+
assertRemovalAuthorization({ steps: diffSchemas({ from: state.from.schema, to: state.to.schema, dialect }), previous: state.from, dialect, namespace: dialect === "sqlite" ? "main" : "public", ...params.acceptIntegrityLoss === undefined ? {} : { acceptIntegrityLoss: params.acceptIntegrityLoss } });
|
|
6850
7132
|
}
|
|
6851
7133
|
assertSecurityAcknowledgement({ securityChanges: [...migration.securityChanges ?? [], ...migrationRlsSecurityChanges({ migration, down: false })], ...params.acceptSecurityChanges === undefined ? {} : { acceptSecurityChanges: params.acceptSecurityChanges } });
|
|
6852
7134
|
}
|
|
@@ -6858,11 +7140,11 @@ function assertMigrationMetadata(params) {
|
|
|
6858
7140
|
assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
|
|
6859
7141
|
}
|
|
6860
7142
|
if (migration.desiredStatePending === true)
|
|
6861
|
-
throw new
|
|
7143
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration ${migration.name} has unfinalized desired state; finalize its reviewed before/after declarations before applying`, meta: { reason: "desired-state-unfinalized", migration: migration.name } });
|
|
6862
7144
|
if (migration.desiredState !== undefined) {
|
|
6863
7145
|
assertDesiredState({ value: migration.desiredState, migration: migration.name });
|
|
6864
7146
|
if (migration.desiredState.dialect !== dialect)
|
|
6865
|
-
throw new
|
|
7147
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch", migration: migration.name } });
|
|
6866
7148
|
}
|
|
6867
7149
|
assertNativeMigrationMetadata({ migration, sql: migration.sql, dialect });
|
|
6868
7150
|
assertExecutable({ migration, execution: migration.execution, sql: migration.sql, dialect });
|
|
@@ -6871,23 +7153,19 @@ async function applyMigrations(params) {
|
|
|
6871
7153
|
const { executor, migrations } = params;
|
|
6872
7154
|
const dialect = params.dialect ?? "postgres";
|
|
6873
7155
|
if (params.history !== undefined && params.history !== "complete" && params.history !== "subset")
|
|
6874
|
-
throw new
|
|
7156
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Unknown migration history validation mode", meta: { reason: "history-mode-invalid" } });
|
|
6875
7157
|
if (params.history === "complete")
|
|
6876
7158
|
assertDistinctMigrationNames({ migrations });
|
|
6877
7159
|
for (const migration of migrations)
|
|
6878
7160
|
assertMigrationMetadata({ migration, dialect });
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
try {
|
|
6884
|
-
const moduleRecords = migrations.some((migration) => migration.moduleOrigin !== undefined) ? await readModuleJournal({ executor, dialect }) : undefined;
|
|
6885
|
-
const recorded = moduleRecords === undefined ? await recordedRows({ executor, dialect }) : new Map([...moduleRecords].map(([name, record3]) => [name, record3.row]));
|
|
6886
|
-
if (moduleRecords !== undefined) {
|
|
7161
|
+
return withMigrationLock({ executor, dialect, run: async (executor) => {
|
|
7162
|
+
let moduleRecords = await readModuleJournal({ executor, dialect });
|
|
7163
|
+
const recorded = new Map([...moduleRecords].map(([name, record]) => [name, record.row]));
|
|
7164
|
+
if (migrations.some((migration) => migration.moduleOrigin !== undefined) || [...moduleRecords.values()].some((record) => record.entry !== undefined)) {
|
|
6887
7165
|
assertModuleUpgradeHistory({ migrations, records: moduleRecords });
|
|
6888
7166
|
const namespace = await migrationNamespace({ executor, dialect });
|
|
6889
7167
|
if (migrations.some((migration) => migration.moduleOrigin !== undefined && moduleNamespace({ origin: migration.moduleOrigin }) !== namespace))
|
|
6890
|
-
throw new
|
|
7168
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Module placement differs from the active database namespace", meta: { reason: "module-placement-mismatch" } });
|
|
6891
7169
|
}
|
|
6892
7170
|
if (params.history === "complete")
|
|
6893
7171
|
assertCompleteHistory({ migrations, recorded, dialect });
|
|
@@ -6898,7 +7176,7 @@ async function applyMigrations(params) {
|
|
|
6898
7176
|
});
|
|
6899
7177
|
validateMigrationReplay({ ...params, migrations: pending, dialect });
|
|
6900
7178
|
await ensureMigrationsTable({ executor, dialect });
|
|
6901
|
-
if (
|
|
7179
|
+
if (migrations.some((migration) => migration.moduleOrigin !== undefined))
|
|
6902
7180
|
await ensureModuleJournal({ executor, dialect });
|
|
6903
7181
|
const applied = [];
|
|
6904
7182
|
const skipped = [];
|
|
@@ -6908,7 +7186,7 @@ async function applyMigrations(params) {
|
|
|
6908
7186
|
await assertLiveModuleOwnership({ executor, origin: migration.moduleOrigin, resume: stored !== undefined });
|
|
6909
7187
|
if (stored !== undefined) {
|
|
6910
7188
|
if (stored.statementIndex < 0) {
|
|
6911
|
-
throw new
|
|
7189
|
+
throw new VibeError32({
|
|
6912
7190
|
code: "VIBE_MIGRATION",
|
|
6913
7191
|
message: `migration "${migration.name}" is mid-rollback (${-stored.statementIndex - 1} down statement(s) done) \u2014 finish it with \`migrate down\` or repair with resolveMigration before applying`,
|
|
6914
7192
|
meta: { migration: migration.name, statementIndex: stored.statementIndex }
|
|
@@ -6929,11 +7207,14 @@ async function applyMigrations(params) {
|
|
|
6929
7207
|
dialect,
|
|
6930
7208
|
migration,
|
|
6931
7209
|
start: stored.statementIndex,
|
|
7210
|
+
moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }),
|
|
6932
7211
|
...params.probe === undefined ? {} : { probe: params.probe },
|
|
6933
7212
|
insertRecord: false,
|
|
6934
7213
|
...stored.error === undefined ? {} : { storedError: stored.error }
|
|
6935
7214
|
})
|
|
6936
7215
|
});
|
|
7216
|
+
if (migration.moduleOrigin !== undefined)
|
|
7217
|
+
moduleRecords = await readModuleJournal({ executor, dialect });
|
|
6937
7218
|
applied.push(migration.name);
|
|
6938
7219
|
continue;
|
|
6939
7220
|
}
|
|
@@ -6942,11 +7223,13 @@ async function applyMigrations(params) {
|
|
|
6942
7223
|
executor,
|
|
6943
7224
|
dialect,
|
|
6944
7225
|
...migration.execution === undefined ? {} : { execution: migration.execution },
|
|
6945
|
-
run: () => runNonTransactional({ executor, dialect, migration, start: 0, insertRecord: true, ...params.probe === undefined ? {} : { probe: params.probe } })
|
|
7226
|
+
run: () => runNonTransactional({ executor, dialect, migration, moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }), start: 0, insertRecord: true, ...params.probe === undefined ? {} : { probe: params.probe } })
|
|
6946
7227
|
});
|
|
6947
7228
|
} else {
|
|
6948
|
-
await runTransactional({ executor, dialect, migration, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
7229
|
+
await runTransactional({ executor, dialect, migration, moduleOrigins: applicationModuleOrigins({ migration, records: moduleRecords }), ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
6949
7230
|
}
|
|
7231
|
+
if (migration.moduleOrigin !== undefined)
|
|
7232
|
+
moduleRecords = await readModuleJournal({ executor, dialect });
|
|
6950
7233
|
applied.push(migration.name);
|
|
6951
7234
|
}
|
|
6952
7235
|
const final = migrations.at(-1);
|
|
@@ -6961,9 +7244,7 @@ async function applyMigrations(params) {
|
|
|
6961
7244
|
}
|
|
6962
7245
|
}
|
|
6963
7246
|
return { applied, skipped };
|
|
6964
|
-
}
|
|
6965
|
-
await releaseLock({ executor, dialect });
|
|
6966
|
-
}
|
|
7247
|
+
} });
|
|
6967
7248
|
}
|
|
6968
7249
|
async function migrationStatus(params) {
|
|
6969
7250
|
const { executor, migrations } = params;
|
|
@@ -7033,19 +7314,14 @@ function classifyJournal(params) {
|
|
|
7033
7314
|
return journal;
|
|
7034
7315
|
}
|
|
7035
7316
|
async function resolveMigration(params) {
|
|
7036
|
-
if (params.executor.withSession !== undefined) {
|
|
7037
|
-
return params.executor.withSession({ run: (executor2) => resolveMigration({ ...params, executor: executor2 }) });
|
|
7038
|
-
}
|
|
7039
|
-
const { executor } = params;
|
|
7040
7317
|
const dialect = params.dialect ?? "postgres";
|
|
7041
|
-
await
|
|
7042
|
-
await repairMigration({ ...params, executor, dialect }).finally(() => releaseLock({ executor, dialect }));
|
|
7318
|
+
await withMigrationLock({ executor: params.executor, dialect, run: (executor) => repairMigration({ ...params, executor, dialect }) });
|
|
7043
7319
|
}
|
|
7044
7320
|
async function repairMigration(params) {
|
|
7045
7321
|
const { executor, name, dialect } = params;
|
|
7046
7322
|
const moduleRecord = (await readModuleJournal({ executor, dialect })).get(name);
|
|
7047
7323
|
if (moduleRecord?.entry !== undefined)
|
|
7048
|
-
throw new
|
|
7324
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Module history cannot be marked applied or deleted without verified module recovery", meta: { reason: "module-resolution-required", moduleId: moduleRecord.entry.migration.moduleOrigin.moduleId } });
|
|
7049
7325
|
await ensureMigrationsTable({ executor, dialect });
|
|
7050
7326
|
if (params.as === "applied") {
|
|
7051
7327
|
const insert = `INSERT INTO ${q5(dialect, MIGRATIONS_TABLE_NAME)} (${q5(dialect, "name")}, ${q5(dialect, "checksum")}, ${q5(dialect, "statement_index")}) VALUES (${ph(dialect, 1)}, ${ph(dialect, 2)}, ${RESOLVED_STATEMENT_INDEX})`;
|
|
@@ -7062,38 +7338,30 @@ async function rollbackMigration(params) {
|
|
|
7062
7338
|
const { executor, migration } = params;
|
|
7063
7339
|
const dialect = params.dialect ?? "postgres";
|
|
7064
7340
|
const downSql = validateRollbackFile({ ...params, dialect });
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
}
|
|
7068
|
-
await acquireLock({ executor, dialect });
|
|
7069
|
-
await Promise.resolve().then(async () => {
|
|
7070
|
-
const recorded = await recordedRows({ executor, dialect, modules: migration.moduleOrigin !== undefined });
|
|
7341
|
+
await withMigrationLock({ executor, dialect, run: async (executor) => {
|
|
7342
|
+
const recorded = await recordedRows({ executor, dialect, modules: true });
|
|
7071
7343
|
const stored = assertRollbackRecord({ migration, stored: recorded.get(migration.name), dialect });
|
|
7072
7344
|
const newest = [...recorded.keys()].at(-1);
|
|
7073
7345
|
if (newest !== migration.name)
|
|
7074
|
-
throw new
|
|
7346
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${migration.name}" is not the newest recorded migration; select rollback again`, meta: { reason: "rollback-not-latest", migration: migration.name, newest } });
|
|
7075
7347
|
await validateRollbackRecovery({ executor, migration, downSql, stored, dialect });
|
|
7076
|
-
await executeRollback({ ...params, dialect, downSql, stored });
|
|
7077
|
-
}
|
|
7348
|
+
await executeRollback({ ...params, executor, dialect, downSql, stored });
|
|
7349
|
+
} });
|
|
7078
7350
|
}
|
|
7079
7351
|
async function rollbackMigrations(params) {
|
|
7080
7352
|
const { executor, migrations } = params;
|
|
7081
7353
|
const dialect = params.dialect ?? "postgres";
|
|
7082
7354
|
assertDistinctMigrationNames({ migrations });
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
}
|
|
7086
|
-
await acquireLock({ executor, dialect });
|
|
7087
|
-
return Promise.resolve().then(async () => {
|
|
7088
|
-
const recorded = await recordedRows({ executor, dialect, modules: migrations.some((migration) => migration.moduleOrigin !== undefined) });
|
|
7355
|
+
return withMigrationLock({ executor, dialect, run: async (executor) => {
|
|
7356
|
+
const recorded = await recordedRows({ executor, dialect, modules: true });
|
|
7089
7357
|
const journal = classifyJournal({ migrations, recorded, dialect });
|
|
7090
7358
|
const byName = new Map(migrations.map((migration) => [migration.name, migration]));
|
|
7091
7359
|
let start = Math.max(0, journal.length - 1);
|
|
7092
7360
|
if (params.to !== undefined) {
|
|
7093
|
-
const target = journal.findIndex((
|
|
7361
|
+
const target = journal.findIndex((row) => row.name === params.to);
|
|
7094
7362
|
const row = journal[target];
|
|
7095
7363
|
if (row?.state !== "applied")
|
|
7096
|
-
throw new
|
|
7364
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Rollback target "${params.to}" is not a known applied migration`, meta: { reason: "rollback-target-invalid", to: params.to, state: row?.state } });
|
|
7097
7365
|
assertMigrationMetadata({ migration: byName.get(row.name), dialect });
|
|
7098
7366
|
start = target + 1;
|
|
7099
7367
|
}
|
|
@@ -7101,7 +7369,7 @@ async function rollbackMigrations(params) {
|
|
|
7101
7369
|
for (const row of journal.slice(start).reverse()) {
|
|
7102
7370
|
const migration = byName.get(row.name);
|
|
7103
7371
|
if (migration === undefined)
|
|
7104
|
-
throw new
|
|
7372
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Cannot roll back "${row.name}" without its migration file`, meta: { reason: "rollback-file-missing", migration: row.name } });
|
|
7105
7373
|
const stored = assertRollbackRecord({ migration, stored: recorded.get(row.name), dialect });
|
|
7106
7374
|
const downSql = validateRollbackFile({ ...params, migration, dialect });
|
|
7107
7375
|
plan.push({ migration, downSql, stored });
|
|
@@ -7110,28 +7378,28 @@ async function rollbackMigrations(params) {
|
|
|
7110
7378
|
await validateRollbackRecovery({ executor, dialect, ...entry });
|
|
7111
7379
|
const rolledBack = [];
|
|
7112
7380
|
for (const entry of plan) {
|
|
7113
|
-
await executeRollback({ ...params, dialect, ...entry });
|
|
7381
|
+
await executeRollback({ ...params, executor, dialect, ...entry });
|
|
7114
7382
|
rolledBack.push(entry.migration.name);
|
|
7115
7383
|
}
|
|
7116
7384
|
return { rolledBack };
|
|
7117
|
-
}
|
|
7385
|
+
} });
|
|
7118
7386
|
}
|
|
7119
7387
|
function validateRollbackFile(params) {
|
|
7120
7388
|
const { migration, dialect } = params;
|
|
7121
7389
|
if (migration.desiredStatePending === true)
|
|
7122
|
-
throw new
|
|
7390
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Unfinalized desired state cannot be rolled back", meta: { reason: "desired-state-unfinalized", migration: migration.name } });
|
|
7123
7391
|
if (migration.moduleOrigin !== undefined) {
|
|
7124
7392
|
assertMaterializedModuleMigration({ migration });
|
|
7125
7393
|
assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
|
|
7126
7394
|
}
|
|
7127
7395
|
const downSql = migration.downSql;
|
|
7128
7396
|
if (downSql === undefined)
|
|
7129
|
-
throw new
|
|
7397
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" has no down.sql; roll it back manually, then repair with resolveMigration (as: "rolled-back")`, meta: { reason: "rollback-down-missing", migration: migration.name } });
|
|
7130
7398
|
assertProbeMode({ dialect, probe: params.probe });
|
|
7131
7399
|
if (migration.desiredState !== undefined) {
|
|
7132
7400
|
assertDesiredState({ value: migration.desiredState, migration: migration.name });
|
|
7133
7401
|
if (migration.desiredState.dialect !== dialect)
|
|
7134
|
-
throw new
|
|
7402
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch" } });
|
|
7135
7403
|
assertRemovalAuthorization({ steps: diffSchemas({ from: migration.desiredState.to.schema, to: (migration.desiredState.down ?? migration.desiredState.from).schema, dialect }), previous: migration.desiredState.to, dialect, namespace: dialect === "sqlite" ? "main" : "public", ...params.acceptIntegrityLoss === undefined ? {} : { acceptIntegrityLoss: params.acceptIntegrityLoss } });
|
|
7136
7404
|
}
|
|
7137
7405
|
assertNativeMigrationMetadata({ migration, sql: downSql, dialect });
|
|
@@ -7144,10 +7412,10 @@ function validateRollbackFile(params) {
|
|
|
7144
7412
|
function assertRollbackRecord(params) {
|
|
7145
7413
|
const { migration, stored, dialect } = params;
|
|
7146
7414
|
if (stored === undefined)
|
|
7147
|
-
throw new
|
|
7415
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is not recorded as applied; nothing to roll back`, meta: { migration: migration.name } });
|
|
7148
7416
|
assertChecksumMatches({ migration, stored: stored.checksum });
|
|
7149
7417
|
if (stored.statementIndex >= 0 && (!isCompleteRecord({ dialect, migration, stored }) || stored.error !== undefined))
|
|
7150
|
-
throw new
|
|
7418
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is only partially applied or has an unresolved error; its down mirrors the FULL up. Finish or repair with resolveMigration`, meta: { reason: "rollback-partial-up", migration: migration.name, statementIndex: stored.statementIndex } });
|
|
7151
7419
|
return stored;
|
|
7152
7420
|
}
|
|
7153
7421
|
async function validateRollbackRecovery(params) {
|
|
@@ -7156,7 +7424,7 @@ async function validateRollbackRecovery(params) {
|
|
|
7156
7424
|
return;
|
|
7157
7425
|
const start = -stored.statementIndex - 1;
|
|
7158
7426
|
if (start > downSql.length)
|
|
7159
|
-
throw new
|
|
7427
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Rollback progress exceeds its statement count", meta: { reason: "rollback-progress-invalid", migration: migration.name } });
|
|
7160
7428
|
if (dialect === "sqlite" && usesSelfManagedTransactions({ sql: downSql }))
|
|
7161
7429
|
assertSelfManagedResumeSafe({ sql: downSql, start, migration: migration.name, direction: "down" });
|
|
7162
7430
|
if (!marksInflight({ dialect, execution: migration.downExecution }) || start >= downSql.length)
|
|
@@ -7169,16 +7437,17 @@ async function validateRollbackRecovery(params) {
|
|
|
7169
7437
|
}
|
|
7170
7438
|
async function executeRollback(params) {
|
|
7171
7439
|
const { executor, dialect, migration, downSql, stored } = params;
|
|
7440
|
+
const moduleOrigins = applicationModuleOrigins({ migration, records: await readModuleJournal({ executor, dialect }) });
|
|
7172
7441
|
if (usesStatementBookkeeping({ dialect, sql: downSql, ...migration.downExecution === undefined ? {} : { execution: migration.downExecution } })) {
|
|
7173
7442
|
const start = stored.statementIndex < 0 ? -stored.statementIndex - 1 : 0;
|
|
7174
7443
|
await withSessionLimits({
|
|
7175
7444
|
executor,
|
|
7176
7445
|
dialect,
|
|
7177
7446
|
execution: migration.downExecution,
|
|
7178
|
-
run: () => runNonTransactionalDown({ executor, dialect, migration, downSql, start, probe: params.probe, storedError: stored.error })
|
|
7447
|
+
run: () => runNonTransactionalDown({ executor, dialect, migration, moduleOrigins, downSql, start, probe: params.probe, storedError: stored.error })
|
|
7179
7448
|
});
|
|
7180
7449
|
} else
|
|
7181
|
-
await runTransactionalDown({ executor, dialect, migration, downSql, probe: params.probe });
|
|
7450
|
+
await runTransactionalDown({ executor, dialect, migration, moduleOrigins, downSql, probe: params.probe });
|
|
7182
7451
|
}
|
|
7183
7452
|
async function runTransactionalDown(params) {
|
|
7184
7453
|
const { executor, dialect, migration, downSql } = params;
|
|
@@ -7190,14 +7459,15 @@ async function runTransactionalDown(params) {
|
|
|
7190
7459
|
const limits = migration.downExecution;
|
|
7191
7460
|
if (wrap && dialect === "postgres" && limits !== undefined) {
|
|
7192
7461
|
if ((limits.lockTimeoutMs ?? 0) > 0)
|
|
7193
|
-
await executor({ text: renderMigrationSessionSet({ dialect:
|
|
7462
|
+
await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "lock_timeout", milliseconds: limits.lockTimeoutMs, local: true }).text });
|
|
7194
7463
|
if ((limits.statementTimeoutMs ?? 0) > 0)
|
|
7195
|
-
await executor({ text: renderMigrationSessionSet({ dialect:
|
|
7464
|
+
await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "statement_timeout", milliseconds: limits.statementTimeoutMs, local: true }).text });
|
|
7196
7465
|
}
|
|
7197
7466
|
if (migration.desiredState !== undefined)
|
|
7198
7467
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7199
7468
|
if (migration.rls !== undefined)
|
|
7200
7469
|
await introspectPostgres({ executor, rlsSchema: migration.rls.to, inTransaction: true, requireInstalledRls: true });
|
|
7470
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7201
7471
|
for (const statement of downSql) {
|
|
7202
7472
|
await executeStatement({ executor, statement, dialect });
|
|
7203
7473
|
}
|
|
@@ -7205,6 +7475,7 @@ async function runTransactionalDown(params) {
|
|
|
7205
7475
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.to, to: migration.desiredState.down ?? migration.desiredState.from, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7206
7476
|
if (migration.rls !== undefined)
|
|
7207
7477
|
await introspectPostgres({ executor, rlsSchema: migration.rls.from, inTransaction: true, requireInstalledRls: true });
|
|
7478
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7208
7479
|
await executor({
|
|
7209
7480
|
text: `DELETE FROM ${q5(dialect, MIGRATIONS_TABLE_NAME)} WHERE ${q5(dialect, "name")} = ${ph(dialect, 1)}`,
|
|
7210
7481
|
values: [migration.name]
|
|
@@ -7219,7 +7490,7 @@ async function runTransactionalDown(params) {
|
|
|
7219
7490
|
} else {
|
|
7220
7491
|
await cleanupSelfManagedFailure({ executor });
|
|
7221
7492
|
}
|
|
7222
|
-
throw new
|
|
7493
|
+
throw new VibeError32({
|
|
7223
7494
|
code: "VIBE_MIGRATION",
|
|
7224
7495
|
message: `rollback of "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
7225
7496
|
meta: { migration: migration.name, ...timeoutDiagnostic({ error }) },
|
|
@@ -7251,6 +7522,7 @@ async function runNonTransactionalDown(params) {
|
|
|
7251
7522
|
throw uncertainStatement({ migration: migration.name, statements: downSql, index: start, dialect, direction: "down" });
|
|
7252
7523
|
}
|
|
7253
7524
|
const resumeState = resumeOperation === undefined ? undefined : await establishOperationState({ executor, dialect, operation: resumeOperation });
|
|
7525
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7254
7526
|
if (start === 0) {
|
|
7255
7527
|
if (migration.desiredState !== undefined)
|
|
7256
7528
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
@@ -7260,6 +7532,8 @@ async function runNonTransactionalDown(params) {
|
|
|
7260
7532
|
if (resumeOperation !== undefined && resumeState === "satisfied") {
|
|
7261
7533
|
if (start + 1 === downSql.length && migration.desiredState !== undefined)
|
|
7262
7534
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.to, to: migration.desiredState.down ?? migration.desiredState.from, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7535
|
+
if (start + 1 === downSql.length)
|
|
7536
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7263
7537
|
await recordProgress(start + 1);
|
|
7264
7538
|
cursor = start + 1;
|
|
7265
7539
|
}
|
|
@@ -7278,11 +7552,14 @@ async function runNonTransactionalDown(params) {
|
|
|
7278
7552
|
if (commitsBlock) {
|
|
7279
7553
|
if (migration.desiredState !== undefined && downSql.slice(index + 1).every((statement) => /^\s*PRAGMA\s+foreign_keys\s*=\s*ON\s*;?\s*$/i.test(statement)))
|
|
7280
7554
|
assertMigrationState({ report: await verifySqliteTransitionBeforeRestore({ executor, dialect, from: migration.desiredState.to, to: migration.desiredState.down ?? migration.desiredState.from, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7555
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe, sqliteRestorePending: true });
|
|
7281
7556
|
await recordProgress(index + 1);
|
|
7282
7557
|
}
|
|
7283
7558
|
await executeStatement({ executor, statement: downSql[index], dialect });
|
|
7284
7559
|
if (index === downSql.length - 1 && migration.desiredState !== undefined)
|
|
7285
7560
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.to, to: migration.desiredState.down ?? migration.desiredState.from, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7561
|
+
if (index === downSql.length - 1)
|
|
7562
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7286
7563
|
if (atomic) {
|
|
7287
7564
|
await recordProgress(index + 1);
|
|
7288
7565
|
await executor({ text: "COMMIT" });
|
|
@@ -7296,7 +7573,7 @@ async function runNonTransactionalDown(params) {
|
|
|
7296
7573
|
values: [marks ? `${INFLIGHT_PREFIX}${index}` : error instanceof Error ? error.message : String(error), migration.name]
|
|
7297
7574
|
});
|
|
7298
7575
|
} catch {}
|
|
7299
|
-
throw new
|
|
7576
|
+
throw new VibeError32({
|
|
7300
7577
|
code: "VIBE_MIGRATION",
|
|
7301
7578
|
message: `rollback of "${migration.name}" failed at down statement ${index + 1} of ${downSql.length}: ${error instanceof Error ? error.message : String(error)}. ${marks ? "The statement outcome is unknown; inspect its effects before resolving or retrying." : "Progress is recorded; re-running the rollback resumes from this statement."}`,
|
|
7302
7579
|
meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeoutDiagnostic({ error }) },
|
|
@@ -7323,11 +7600,12 @@ async function runTransactional(params) {
|
|
|
7323
7600
|
const limits = migration.execution;
|
|
7324
7601
|
if (wrap && dialect === "postgres" && limits !== undefined) {
|
|
7325
7602
|
if ((limits.lockTimeoutMs ?? 0) > 0)
|
|
7326
|
-
await executor({ text: renderMigrationSessionSet({ dialect:
|
|
7603
|
+
await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "lock_timeout", milliseconds: limits.lockTimeoutMs, local: true }).text });
|
|
7327
7604
|
if ((limits.statementTimeoutMs ?? 0) > 0)
|
|
7328
|
-
await executor({ text: renderMigrationSessionSet({ dialect:
|
|
7605
|
+
await executor({ text: renderMigrationSessionSet({ dialect: getSqlDialect7({ dialect }), setting: "statement_timeout", milliseconds: limits.statementTimeoutMs, local: true }).text });
|
|
7329
7606
|
}
|
|
7330
7607
|
await assertForwardStart({ executor, dialect, migration, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
7608
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7331
7609
|
if (migration.rls !== undefined)
|
|
7332
7610
|
await introspectPostgres({ executor, rlsSchema: migration.rls.from, inTransaction: true, requireInstalledRls: true });
|
|
7333
7611
|
let statementIndex = 0;
|
|
@@ -7339,6 +7617,7 @@ async function runTransactional(params) {
|
|
|
7339
7617
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7340
7618
|
if (migration.rls !== undefined)
|
|
7341
7619
|
await introspectPostgres({ executor, rlsSchema: migration.rls.to, inTransaction: true, requireInstalledRls: true });
|
|
7620
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7342
7621
|
if (migration.moduleOrigin !== undefined)
|
|
7343
7622
|
await insertModuleJournal({ executor, dialect, migration, index: statementIndex, complete: true });
|
|
7344
7623
|
else
|
|
@@ -7356,7 +7635,7 @@ async function runTransactional(params) {
|
|
|
7356
7635
|
} else {
|
|
7357
7636
|
await cleanupSelfManagedFailure({ executor });
|
|
7358
7637
|
}
|
|
7359
|
-
throw new
|
|
7638
|
+
throw new VibeError32({
|
|
7360
7639
|
code: "VIBE_MIGRATION",
|
|
7361
7640
|
message: `migration "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
7362
7641
|
meta: { migration: migration.name },
|
|
@@ -7376,6 +7655,7 @@ async function runNonTransactional(params) {
|
|
|
7376
7655
|
const declared = execution?.transaction === "none";
|
|
7377
7656
|
const marks = marksInflight({ dialect, ...execution === undefined ? {} : { execution } });
|
|
7378
7657
|
const start = selfManaged ? selfManagedResumeStart({ sql: migration.sql, start: params.start }) : params.start;
|
|
7658
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7379
7659
|
if (insertRecord) {
|
|
7380
7660
|
if (migration.desiredState !== undefined) {
|
|
7381
7661
|
await assertForwardStart({ executor, dialect, migration, ...params.probe === undefined ? {} : { probe: params.probe } });
|
|
@@ -7400,10 +7680,12 @@ async function runNonTransactional(params) {
|
|
|
7400
7680
|
if (marks && !insertRecord && start < migration.sql.length) {
|
|
7401
7681
|
const operation = declared ? operationFor({ ...execution === undefined ? {} : { execution }, statement: start }) : undefined;
|
|
7402
7682
|
if (operation !== undefined) {
|
|
7403
|
-
const
|
|
7404
|
-
if (
|
|
7683
|
+
const state = await establishOperationState({ executor, dialect, operation });
|
|
7684
|
+
if (state === "satisfied") {
|
|
7405
7685
|
if (start + 1 === migration.sql.length && migration.desiredState !== undefined)
|
|
7406
7686
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7687
|
+
if (start + 1 === migration.sql.length)
|
|
7688
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7407
7689
|
await checkpoint(start + 1);
|
|
7408
7690
|
cursor = start + 1;
|
|
7409
7691
|
}
|
|
@@ -7426,11 +7708,14 @@ async function runNonTransactional(params) {
|
|
|
7426
7708
|
if (commitsBlock) {
|
|
7427
7709
|
if (migration.desiredState !== undefined && migration.sql.slice(index + 1).every((statement) => /^\s*PRAGMA\s+foreign_keys\s*=\s*ON\s*;?\s*$/i.test(statement)))
|
|
7428
7710
|
assertMigrationState({ report: await verifySqliteTransitionBeforeRestore({ executor, dialect, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7711
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe, sqliteRestorePending: true });
|
|
7429
7712
|
await checkpoint(index + 1);
|
|
7430
7713
|
}
|
|
7431
7714
|
await executeStatement({ executor, statement: migration.sql[index], dialect });
|
|
7432
7715
|
if (index === migration.sql.length - 1 && migration.desiredState !== undefined)
|
|
7433
7716
|
assertMigrationState({ report: await verifyMigrationTransition({ executor, from: migration.desiredState.from, to: migration.desiredState.to, removals: migration.desiredState.unmanagedRemovals, dialect, ...params.probe === undefined ? {} : { probe: params.probe } }) });
|
|
7717
|
+
if (index === migration.sql.length - 1)
|
|
7718
|
+
await assertApplicationModuleState({ executor, origins: params.moduleOrigins, probe: params.probe });
|
|
7434
7719
|
if (atomic) {
|
|
7435
7720
|
await checkpoint(index + 1);
|
|
7436
7721
|
await executor({ text: "COMMIT" });
|
|
@@ -7446,7 +7731,7 @@ async function runNonTransactional(params) {
|
|
|
7446
7731
|
} catch {}
|
|
7447
7732
|
const sqlState = sqlStateOf(error);
|
|
7448
7733
|
const timeout = sqlState === "55P03" ? "lock-timeout" : sqlState === "57014" ? "statement-timeout" : undefined;
|
|
7449
|
-
throw new
|
|
7734
|
+
throw new VibeError32({
|
|
7450
7735
|
code: "VIBE_MIGRATION",
|
|
7451
7736
|
message: timeout === "lock-timeout" ? `migration "${migration.name}" gave up waiting for a lock at statement ${index + 1} of ${migration.sql.length} (lock_timeout ${String(execution?.lockTimeoutMs ?? 0)}ms, SQLSTATE 55P03). Another session holds a conflicting lock; inspect the declared operation before retrying.` : timeout === "statement-timeout" ? `migration "${migration.name}" exceeded its statement_timeout at statement ${index + 1} of ${migration.sql.length} (${String(execution?.statementTimeoutMs ?? 0)}ms, SQLSTATE 57014). Inspect the declared operation before retrying.` : `migration "${migration.name}" failed at statement ${index + 1} of ${migration.sql.length}: ${error instanceof Error ? error.message : String(error)}. ${marks ? "The statement outcome is unknown; inspect its effects before resolving or retrying." : "Progress is recorded; re-running applyMigrations resumes from this statement."}`,
|
|
7452
7737
|
meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeout === undefined ? {} : { reason: timeout, sqlState } },
|
|
@@ -7474,7 +7759,7 @@ async function acquireLock(params) {
|
|
|
7474
7759
|
});
|
|
7475
7760
|
const acquired = Number(rows[0]?.vibe_lock) === 1;
|
|
7476
7761
|
if (!acquired) {
|
|
7477
|
-
throw new
|
|
7762
|
+
throw new VibeError32({
|
|
7478
7763
|
code: "VIBE_MIGRATION",
|
|
7479
7764
|
message: `could not acquire the migration lock ('${MYSQL_LOCK_NAME}') within ${MYSQL_LOCK_TIMEOUT_SECONDS}s \u2014 another migration run is in progress`,
|
|
7480
7765
|
meta: { lock: MYSQL_LOCK_NAME }
|
|
@@ -7484,13 +7769,11 @@ async function acquireLock(params) {
|
|
|
7484
7769
|
}
|
|
7485
7770
|
async function releaseLock(params) {
|
|
7486
7771
|
const { executor, dialect } = params;
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
}
|
|
7493
|
-
} catch {}
|
|
7772
|
+
if (dialect === "postgres") {
|
|
7773
|
+
await executor({ text: `SELECT pg_advisory_unlock(${PG_ADVISORY_LOCK_KEY})` });
|
|
7774
|
+
} else if (dialect === "mysql") {
|
|
7775
|
+
await executor({ text: `SELECT RELEASE_LOCK('${MYSQL_LOCK_NAME}')` });
|
|
7776
|
+
}
|
|
7494
7777
|
}
|
|
7495
7778
|
var INFLIGHT_PREFIX = "VIBE_INFLIGHT:";
|
|
7496
7779
|
function inflightStatement(params) {
|
|
@@ -7511,7 +7794,7 @@ function uncertainStatement(params) {
|
|
|
7511
7794
|
const subject = direction === "down" ? `rollback of "${migration}"` : `migration "${migration}"`;
|
|
7512
7795
|
const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${statements.length}`;
|
|
7513
7796
|
const why = dialect === "mysql" ? "MySQL commits every statement on its own, so nothing outside the statement records whether it finished." : "That statement declares no operation whose effect recovery can read back from the catalog.";
|
|
7514
|
-
return new
|
|
7797
|
+
return new VibeError32({
|
|
7515
7798
|
code: "VIBE_MIGRATION",
|
|
7516
7799
|
message: `${subject} was interrupted while ${position} was running. ${why} ` + "It may or may not have committed, and replaying it could apply it twice, so VibeORM stops here instead of guessing. " + 'Inspect what that statement did to the database, then follow "Recovering from a stopped migration" in docs/migrations.md: ' + `clear the \`error\` column of this migration's journal row if the statement did NOT land, set \`statement_index\` to ${direction === "down" ? -(index + 2) : index + 1} if it DID, ` + "or finish the whole migration by hand and record that with `vibeorm migrate resolve --applied`. " + `The statement was: ${statements[index].slice(0, 200)}`,
|
|
7517
7800
|
meta: {
|
|
@@ -7529,7 +7812,7 @@ function assertExecutable(params) {
|
|
|
7529
7812
|
if (execution === undefined || isDefaultExecution({ execution }))
|
|
7530
7813
|
return;
|
|
7531
7814
|
if (dialect !== "postgres") {
|
|
7532
|
-
throw new
|
|
7815
|
+
throw new VibeError32({
|
|
7533
7816
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
7534
7817
|
message: `migration "${migration.name}" declares explicit execution semantics (transaction: "${execution.transaction}"), which only the postgres runner implements \u2014 ${dialect} has no separate nontransactional regime to opt into`,
|
|
7535
7818
|
meta: { reason: "nontransactional-unsupported", dialect, migration: migration.name, feature: "nontransactionalMigrations" }
|
|
@@ -7552,7 +7835,7 @@ async function withSessionLimits(params) {
|
|
|
7552
7835
|
const statement = execution?.statementTimeoutMs ?? 0;
|
|
7553
7836
|
if (dialect !== "postgres" || lock === 0 && statement === 0)
|
|
7554
7837
|
return params.run();
|
|
7555
|
-
const sqlDialect =
|
|
7838
|
+
const sqlDialect = getSqlDialect7({ dialect });
|
|
7556
7839
|
const previous = (await executor({ text: renderMigrationSessionRead({ dialect: sqlDialect }).text, values: [] }))[0];
|
|
7557
7840
|
if (lock > 0)
|
|
7558
7841
|
await executor({ text: renderMigrationSessionSet({ dialect: sqlDialect, setting: "lock_timeout", milliseconds: lock }).text });
|
|
@@ -7571,12 +7854,12 @@ async function withSessionLimits(params) {
|
|
|
7571
7854
|
}
|
|
7572
7855
|
}
|
|
7573
7856
|
function usesStatementBookkeeping(params) {
|
|
7574
|
-
const { dialect, sql
|
|
7857
|
+
const { dialect, sql } = params;
|
|
7575
7858
|
if (params.execution?.transaction === "none")
|
|
7576
7859
|
return true;
|
|
7577
7860
|
if (!DIALECT_CAPABILITIES3[dialect].transactionalDdl)
|
|
7578
7861
|
return true;
|
|
7579
|
-
return dialect === "sqlite" && usesSelfManagedTransactions({ sql
|
|
7862
|
+
return dialect === "sqlite" && usesSelfManagedTransactions({ sql });
|
|
7580
7863
|
}
|
|
7581
7864
|
function isCompleteRecord(params) {
|
|
7582
7865
|
const { dialect, migration, stored } = params;
|
|
@@ -7591,7 +7874,7 @@ function isCompleteRecord(params) {
|
|
|
7591
7874
|
async function recordedRows(params) {
|
|
7592
7875
|
const { executor, dialect } = params;
|
|
7593
7876
|
if (params.modules === true)
|
|
7594
|
-
return new Map([...await readModuleJournal({ executor, dialect })].map(([name,
|
|
7877
|
+
return new Map([...await readModuleJournal({ executor, dialect })].map(([name, record]) => [name, record.row]));
|
|
7595
7878
|
const rows = await readJournalRows({ executor, dialect, query: {
|
|
7596
7879
|
text: `SELECT ${q5(dialect, "name")}, ${q5(dialect, "checksum")}, ${q5(dialect, "statement_index")}, ${q5(dialect, "error")} FROM ${q5(dialect, MIGRATIONS_TABLE_NAME)} ORDER BY ${q5(dialect, "id")}`,
|
|
7597
7880
|
values: []
|
|
@@ -7605,7 +7888,7 @@ function assertChecksumMatches(params) {
|
|
|
7605
7888
|
const actual = checksumOf({ migration });
|
|
7606
7889
|
if (stored === actual)
|
|
7607
7890
|
return;
|
|
7608
|
-
throw new
|
|
7891
|
+
throw new VibeError32({
|
|
7609
7892
|
code: "VIBE_MIGRATION",
|
|
7610
7893
|
message: `checksum mismatch for migration "${migration.name}" \u2014 its SQL changed after it was applied (recorded ${stored.slice(0, 12)}\u2026, current ${actual.slice(0, 12)}\u2026)`,
|
|
7611
7894
|
meta: { migration: migration.name, recorded: stored, current: actual }
|
|
@@ -7616,7 +7899,7 @@ function assertResumable(params) {
|
|
|
7616
7899
|
const actual = checksumOf({ migration });
|
|
7617
7900
|
if (stored.checksum === actual)
|
|
7618
7901
|
return;
|
|
7619
|
-
throw new
|
|
7902
|
+
throw new VibeError32({
|
|
7620
7903
|
code: "VIBE_MIGRATION",
|
|
7621
7904
|
message: `migration "${migration.name}" is partially applied (${stored.statementIndex} statement(s) done) and its SQL has CHANGED since \u2014 refusing to resume; restore the original SQL or repair with resolveMigration`,
|
|
7622
7905
|
meta: {
|
|
@@ -7636,7 +7919,7 @@ function assertDistinctMigrationNames(params) {
|
|
|
7636
7919
|
const names = new Set;
|
|
7637
7920
|
for (const migration of params.migrations) {
|
|
7638
7921
|
if (names.has(migration.name))
|
|
7639
|
-
throw new
|
|
7922
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Duplicate migration file identity "${migration.name}"`, meta: { reason: "history-duplicate-file", migration: migration.name } });
|
|
7640
7923
|
names.add(migration.name);
|
|
7641
7924
|
}
|
|
7642
7925
|
}
|
|
@@ -7644,15 +7927,15 @@ function assertCompleteHistory(params) {
|
|
|
7644
7927
|
const byName = new Map(params.migrations.map((migration) => [migration.name, migration]));
|
|
7645
7928
|
for (const name of params.recorded.keys()) {
|
|
7646
7929
|
if (!byName.has(name))
|
|
7647
|
-
throw new
|
|
7930
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Recorded migration "${name}" has no file in the complete chain`, meta: { reason: "history-unknown-file", migration: name } });
|
|
7648
7931
|
}
|
|
7649
7932
|
let index = 0;
|
|
7650
7933
|
for (const [name, stored] of params.recorded) {
|
|
7651
7934
|
const migration = params.migrations[index];
|
|
7652
7935
|
if (migration?.name !== name)
|
|
7653
|
-
throw new
|
|
7936
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Recorded migration order is not a prefix of the supplied complete chain", meta: { reason: "history-order-conflict", migration: name, expected: migration?.name, position: index } });
|
|
7654
7937
|
if (stored.statementIndex >= 0 && !isCompleteRecord({ dialect: params.dialect, migration, stored }) && index !== params.recorded.size - 1)
|
|
7655
|
-
throw new
|
|
7938
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Partial migration "${name}" precedes later recorded work`, meta: { reason: "history-partial-not-last", migration: name } });
|
|
7656
7939
|
index++;
|
|
7657
7940
|
}
|
|
7658
7941
|
}
|
|
@@ -7661,16 +7944,16 @@ async function assertApplyHistory(params) {
|
|
|
7661
7944
|
const byName = new Map(migrations.map((migration) => [migration.name, migration]));
|
|
7662
7945
|
for (const [name, stored] of recorded) {
|
|
7663
7946
|
if (stored.statementIndex < 0) {
|
|
7664
|
-
throw new
|
|
7947
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${name}" is mid-rollback; finish or resolve it before applying`, meta: { reason: "history-rolling-back", migration: name, statementIndex: stored.statementIndex } });
|
|
7665
7948
|
}
|
|
7666
7949
|
const migration = byName.get(name);
|
|
7667
7950
|
if (migration === undefined) {
|
|
7668
7951
|
if (stored.error !== undefined)
|
|
7669
|
-
throw new
|
|
7952
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error but no supplied file`, meta: { reason: "history-unknown-dirty", migration: name } });
|
|
7670
7953
|
continue;
|
|
7671
7954
|
}
|
|
7672
7955
|
if (isCompleteRecord({ dialect, migration, stored }) && stored.error !== undefined)
|
|
7673
|
-
throw new
|
|
7956
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error`, meta: { reason: "history-dirty", migration: name } });
|
|
7674
7957
|
if (isCompleteRecord({ dialect, migration, stored }))
|
|
7675
7958
|
assertChecksumMatches({ migration, stored: stored.checksum });
|
|
7676
7959
|
else
|
|
@@ -7695,10 +7978,10 @@ async function assertApplyHistory(params) {
|
|
|
7695
7978
|
}
|
|
7696
7979
|
}
|
|
7697
7980
|
function q5(dialect, name) {
|
|
7698
|
-
return
|
|
7981
|
+
return getSqlDialect7({ dialect }).quoteIdent({ name });
|
|
7699
7982
|
}
|
|
7700
7983
|
function ph(dialect, index) {
|
|
7701
|
-
return
|
|
7984
|
+
return getSqlDialect7({ dialect }).placeholder({ index });
|
|
7702
7985
|
}
|
|
7703
7986
|
|
|
7704
7987
|
// packages/migrate/src/fs.ts
|
|
@@ -7706,7 +7989,7 @@ function absent(error) {
|
|
|
7706
7989
|
return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
7707
7990
|
}
|
|
7708
7991
|
function failure(params) {
|
|
7709
|
-
return new
|
|
7992
|
+
return new VibeError33({ code: "VIBE_MIGRATION", message: `Cannot read migration file ${params.path}`, meta: { reason: "migration-file-unreadable", path: params.path }, cause: params.cause });
|
|
7710
7993
|
}
|
|
7711
7994
|
async function optionalFile(params) {
|
|
7712
7995
|
return readFile(params.path, "utf-8").catch((cause) => {
|
|
@@ -7718,15 +8001,15 @@ async function optionalFile(params) {
|
|
|
7718
8001
|
function rlsMetadata(params) {
|
|
7719
8002
|
return Promise.resolve().then(() => {
|
|
7720
8003
|
const metadata = JSON.parse(params.text);
|
|
7721
|
-
const
|
|
7722
|
-
if (metadata?.version !== 1 || typeof metadata.destructive !== "boolean" || metadata.rls === undefined || !
|
|
7723
|
-
throw new
|
|
8004
|
+
const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
8005
|
+
if (metadata?.version !== 1 || typeof metadata.destructive !== "boolean" || metadata.rls === undefined || !strings(metadata.securityChanges) || !strings(metadata.downSecurityChanges) || metadata.sqlChecksum !== migrationChecksum({ sql: params.migration.sql }) || metadata.downChecksum !== migrationChecksum({ sql: params.migration.downSql ?? [] })) {
|
|
8006
|
+
throw new VibeError33({ code: "VIBE_MIGRATION", message: "Invalid native RLS metadata" });
|
|
7724
8007
|
}
|
|
7725
8008
|
assertValidSchemaIR4({ schema: metadata.rls.from });
|
|
7726
8009
|
assertValidSchemaIR4({ schema: metadata.rls.to });
|
|
7727
8010
|
return { ...params.migration, rls: metadata.rls, destructive: metadata.destructive, securityChanges: metadata.securityChanges, downSecurityChanges: metadata.downSecurityChanges };
|
|
7728
8011
|
}).catch((cause) => {
|
|
7729
|
-
throw new
|
|
8012
|
+
throw new VibeError33({ code: "VIBE_MIGRATION", message: `Invalid native RLS metadata for migration "${params.migration.name}"`, meta: { reason: "rls-migration-metadata-invalid", migration: params.migration.name }, cause });
|
|
7730
8013
|
});
|
|
7731
8014
|
}
|
|
7732
8015
|
async function loadMigrationFiles(params) {
|
|
@@ -7759,7 +8042,7 @@ async function loadMigrationFiles(params) {
|
|
|
7759
8042
|
const desired = await optionalFile({ path: join(directory, DESIRED_STATE_METADATA.filename) });
|
|
7760
8043
|
if (desired !== undefined) {
|
|
7761
8044
|
const pending = await Promise.resolve().then(() => isUnfinalizedDesiredState({ text: desired })).catch((cause) => {
|
|
7762
|
-
throw new
|
|
8045
|
+
throw new VibeError33({ code: "VIBE_MIGRATION", message: `Malformed desired state for ${name}`, meta: { reason: "desired-state-invalid", migration: name }, cause });
|
|
7763
8046
|
});
|
|
7764
8047
|
migration = pending ? { ...migration, desiredStatePending: true } : { ...migration, desiredState: parseDesiredStateSidecar({ text: desired, migration: name }) };
|
|
7765
8048
|
}
|
|
@@ -7771,9 +8054,9 @@ async function loadMigrationFiles(params) {
|
|
|
7771
8054
|
return migrations;
|
|
7772
8055
|
}
|
|
7773
8056
|
export {
|
|
7774
|
-
|
|
8057
|
+
joinSqlStatements,
|
|
7775
8058
|
loadMigrationFiles,
|
|
7776
|
-
|
|
8059
|
+
splitSqlStatements
|
|
7777
8060
|
};
|
|
7778
8061
|
|
|
7779
|
-
//# debugId=
|
|
8062
|
+
//# debugId=BB17658EE109D81A64756E2164756E21
|