@vibeorm/migrate 2.7.0 → 3.0.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/LICENSE +21 -0
- package/dist/desired-state.d.ts +9 -0
- package/dist/differ.d.ts +7 -0
- package/dist/fs.js +395 -257
- package/dist/fs.js.map +14 -13
- package/dist/index.js +766 -455
- package/dist/index.js.map +16 -14
- package/dist/inspect-objects.d.ts +8 -0
- package/dist/introspect/group.d.ts +6 -0
- package/dist/introspect/mysql.d.ts +4 -3
- package/dist/object-ownership.d.ts +8 -0
- package/dist/sqlite-fts-upgrade.d.ts +64 -0
- package/package.json +6 -4
- package/dist/adoption.d.ts.map +0 -1
- package/dist/artifact-probe.d.ts.map +0 -1
- package/dist/baseline-record.d.ts.map +0 -1
- package/dist/baseline.d.ts.map +0 -1
- package/dist/compare-reference-rls.d.ts.map +0 -1
- package/dist/compare-sqlite-primary-key.d.ts.map +0 -1
- package/dist/compile-desired-state.d.ts.map +0 -1
- package/dist/ddl/drop-order.d.ts.map +0 -1
- package/dist/ddl/mysql.d.ts.map +0 -1
- package/dist/ddl/postgres.d.ts.map +0 -1
- package/dist/ddl/render.d.ts.map +0 -1
- package/dist/ddl/rls.d.ts.map +0 -1
- package/dist/ddl/sqlite.d.ts.map +0 -1
- package/dist/desired-state.d.ts.map +0 -1
- package/dist/differ.d.ts.map +0 -1
- package/dist/down.d.ts.map +0 -1
- package/dist/enum-values.d.ts.map +0 -1
- package/dist/exclusions.d.ts.map +0 -1
- package/dist/execute.d.ts.map +0 -1
- package/dist/execution.d.ts.map +0 -1
- package/dist/extensions.d.ts.map +0 -1
- package/dist/external-dependencies.d.ts.map +0 -1
- package/dist/external-objects.d.ts.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/history.d.ts.map +0 -1
- package/dist/index-predicates.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/inspect-objects.d.ts.map +0 -1
- package/dist/introspect/mysql.d.ts.map +0 -1
- package/dist/introspect/postgres-rls.d.ts.map +0 -1
- package/dist/introspect/postgres.d.ts.map +0 -1
- package/dist/introspect/shared.d.ts.map +0 -1
- package/dist/introspect/sqlite.d.ts.map +0 -1
- package/dist/inventory.d.ts.map +0 -1
- package/dist/journal-read.d.ts.map +0 -1
- package/dist/journal-table.d.ts.map +0 -1
- package/dist/migration-checksum.d.ts.map +0 -1
- package/dist/module-adoption-record.d.ts.map +0 -1
- package/dist/module-adoption-reference.d.ts.map +0 -1
- package/dist/module-adoption.d.ts.map +0 -1
- package/dist/module-bundle.d.ts.map +0 -1
- package/dist/module-export.d.ts.map +0 -1
- package/dist/module-history.d.ts.map +0 -1
- package/dist/module-journal.d.ts.map +0 -1
- package/dist/module-origin.d.ts.map +0 -1
- package/dist/module-ownership.d.ts.map +0 -1
- package/dist/module-pending.d.ts.map +0 -1
- package/dist/module-readiness.d.ts.map +0 -1
- package/dist/mysql-catalog-visibility.d.ts.map +0 -1
- package/dist/mysql-types.d.ts.map +0 -1
- package/dist/normalize.d.ts.map +0 -1
- package/dist/object-ownership.d.ts.map +0 -1
- package/dist/operator-classes.d.ts.map +0 -1
- package/dist/planner.d.ts.map +0 -1
- package/dist/postgres-default-equivalence.d.ts.map +0 -1
- package/dist/postgres-type-identity.d.ts.map +0 -1
- package/dist/postgres-types.d.ts.map +0 -1
- package/dist/push.d.ts.map +0 -1
- package/dist/recovery.d.ts.map +0 -1
- package/dist/reference-state.d.ts.map +0 -1
- package/dist/relations.d.ts.map +0 -1
- package/dist/renames.d.ts.map +0 -1
- package/dist/replay-objects.d.ts.map +0 -1
- package/dist/replay-plan.d.ts.map +0 -1
- package/dist/replay-reference.d.ts.map +0 -1
- package/dist/runner.d.ts.map +0 -1
- package/dist/shadow-dedicated.d.ts.map +0 -1
- package/dist/shadow-guard.d.ts.map +0 -1
- package/dist/shadow-types.d.ts.map +0 -1
- package/dist/shadow.d.ts.map +0 -1
- package/dist/sql-statements.d.ts.map +0 -1
- package/dist/sqlite-types.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/verify-history.d.ts.map +0 -1
package/dist/fs.js
CHANGED
|
@@ -15,16 +15,12 @@ function hasMysqlGlobalCatalogVisibility(params) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// packages/migrate/src/module-ownership.ts
|
|
18
|
-
import { VibeError as
|
|
18
|
+
import { VibeError as VibeError19, sqlObjectKey as sqlObjectKey7, sqlObjectNamespace as sqlObjectNamespace3 } from "@vibeorm/schema";
|
|
19
19
|
import { getSqlDialect as getSqlDialect4, renderModuleDependencyQuery } from "@vibeorm/sql";
|
|
20
20
|
|
|
21
21
|
// packages/migrate/src/desired-state.ts
|
|
22
22
|
import { VibeError, assertValidSchemaIR, isSqlObjectDefinition, isSqlObjectIdentity, providerToDialect, sqlObjectKey } from "@vibeorm/schema";
|
|
23
23
|
var UNFINALIZED_DESIRED_STATE = Object.freeze({ version: 1, status: "unfinalized" });
|
|
24
|
-
function isUnfinalizedDesiredState(params) {
|
|
25
|
-
const value = JSON.parse(params.text);
|
|
26
|
-
return record(value) && value.version === 1 && value.status === "unfinalized" && Object.keys(value).length === 2;
|
|
27
|
-
}
|
|
28
24
|
var DESIRED_STATE_METADATA = Object.freeze({ filename: "desired-state.json", version: 1 });
|
|
29
25
|
function createDesiredState(params) {
|
|
30
26
|
assertValidSchemaIR({ schema: params.schema });
|
|
@@ -107,6 +103,15 @@ function parseDesiredStateSidecar(params) {
|
|
|
107
103
|
return checked.value;
|
|
108
104
|
}, failure: (cause) => invalid({ migration: params.migration, detail: "malformed metadata", cause }) });
|
|
109
105
|
}
|
|
106
|
+
function loadDesiredStateSidecar(params) {
|
|
107
|
+
return parseMigrationMetadata({ text: params.text, parse: (value) => {
|
|
108
|
+
if (record(value) && value.version === 1 && value.status === "unfinalized" && Object.keys(value).length === 2)
|
|
109
|
+
return { desiredStatePending: true };
|
|
110
|
+
const checked = { value, migration: params.migration };
|
|
111
|
+
assertDesiredState(checked);
|
|
112
|
+
return { desiredState: checked.value };
|
|
113
|
+
}, failure: (cause) => invalid({ migration: params.migration, detail: "malformed metadata", cause }) });
|
|
114
|
+
}
|
|
110
115
|
function parseMigrationMetadata(params) {
|
|
111
116
|
try {
|
|
112
117
|
return params.parse(JSON.parse(params.text));
|
|
@@ -781,6 +786,32 @@ function parseMysqlEnumColumnType(params) {
|
|
|
781
786
|
}
|
|
782
787
|
|
|
783
788
|
// packages/migrate/src/normalize.ts
|
|
789
|
+
function firstByName(params) {
|
|
790
|
+
const result = new Map;
|
|
791
|
+
for (const entry of params.entries)
|
|
792
|
+
if (!result.has(entry.name))
|
|
793
|
+
result.set(entry.name, entry);
|
|
794
|
+
return result;
|
|
795
|
+
}
|
|
796
|
+
function normalizationLookup(params) {
|
|
797
|
+
const enums = new Map;
|
|
798
|
+
const scopedEnums = new Map;
|
|
799
|
+
for (const entry of params.schema.enums) {
|
|
800
|
+
const namespace = qualifiedSqlName3({ name: entry.name, namespace: entry.namespace }).namespace;
|
|
801
|
+
for (const name of new Set([entry.name, entry.dbName ?? entry.name])) {
|
|
802
|
+
if (!enums.has(name))
|
|
803
|
+
enums.set(name, entry);
|
|
804
|
+
const key = JSON.stringify([namespace, name]);
|
|
805
|
+
if (!scopedEnums.has(key))
|
|
806
|
+
scopedEnums.set(key, entry);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return { models: firstByName({ entries: params.schema.models }), fields: new Map(params.schema.models.map((model) => [model, firstByName({ entries: model.fields })])), enums, scopedEnums };
|
|
810
|
+
}
|
|
811
|
+
function lookupFieldName(params) {
|
|
812
|
+
const field = params.lookup.fields.get(params.model)?.get(params.name);
|
|
813
|
+
return field?.dbName ?? field?.name ?? params.name;
|
|
814
|
+
}
|
|
784
815
|
function normalizeSchema(params) {
|
|
785
816
|
const { schema } = params;
|
|
786
817
|
const dialect = params.dialect ?? "postgres";
|
|
@@ -793,6 +824,7 @@ function normalizeSchema(params) {
|
|
|
793
824
|
continue;
|
|
794
825
|
enums.set(physicalNameKey({ name: enumDef.dbName ?? enumDef.name, namespace: enumDef.namespace, dialect }), enumDef);
|
|
795
826
|
}
|
|
827
|
+
const lookup = normalizationLookup({ schema });
|
|
796
828
|
const tables = new Map;
|
|
797
829
|
const views = new Map;
|
|
798
830
|
for (const model of schema.models) {
|
|
@@ -802,25 +834,25 @@ function normalizeSchema(params) {
|
|
|
802
834
|
views.set(physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace, dialect }), model);
|
|
803
835
|
continue;
|
|
804
836
|
}
|
|
805
|
-
const table = normalizeModel({ model,
|
|
837
|
+
const table = normalizeModel({ model, lookup, dialect, enums });
|
|
806
838
|
tables.set(physicalNameKey({ name: table.table, namespace: table.namespace, dialect }), table);
|
|
807
839
|
}
|
|
808
840
|
const joinTables = new Map;
|
|
809
841
|
for (const pair of detectImplicitM2MPairs({ schema })) {
|
|
810
|
-
const join = normalizeJoinTable({ pair,
|
|
842
|
+
const join = normalizeJoinTable({ pair, lookup, dialect });
|
|
811
843
|
joinTables.set(physicalNameKey({ name: join.table, namespace: join.namespace, dialect }), join);
|
|
812
844
|
}
|
|
813
845
|
return { tables, joinTables, enums, views };
|
|
814
846
|
}
|
|
815
847
|
function physicalEnumField(params) {
|
|
816
|
-
const { field,
|
|
848
|
+
const { field, lookup, dialect, table } = params;
|
|
817
849
|
if (!isEnumType5(field.type))
|
|
818
850
|
return field;
|
|
819
851
|
const type = field.type;
|
|
820
852
|
if (type.namespace !== undefined)
|
|
821
853
|
assertSqlNamespace({ namespace: type.namespace, dialect });
|
|
822
854
|
const name = type.enum;
|
|
823
|
-
const enumDef =
|
|
855
|
+
const enumDef = type.namespace === undefined ? lookup.enums.get(name) : lookup.scopedEnums.get(JSON.stringify([type.namespace, name]));
|
|
824
856
|
if (enumDef === undefined)
|
|
825
857
|
return field;
|
|
826
858
|
if (enumStorageOf({ enumDef }) !== "string") {
|
|
@@ -885,48 +917,49 @@ function defaultFingerprintFor(params) {
|
|
|
885
917
|
}
|
|
886
918
|
}
|
|
887
919
|
function normalizeModel(params) {
|
|
888
|
-
const { model,
|
|
920
|
+
const { model, lookup, dialect, enums } = params;
|
|
889
921
|
const table = model.dbName ?? model.name;
|
|
890
922
|
const columns = new Map;
|
|
891
923
|
for (const logicalField of model.fields) {
|
|
892
|
-
const field = physicalEnumField({ field: logicalField,
|
|
924
|
+
const field = physicalEnumField({ field: logicalField, lookup, dialect, table });
|
|
893
925
|
const name = field.dbName ?? field.name;
|
|
926
|
+
const typeKey = columnTypeKeyFor({ field, dialect, enums });
|
|
894
927
|
columns.set(name, {
|
|
895
928
|
name,
|
|
896
929
|
field,
|
|
897
|
-
typeKey
|
|
898
|
-
exactTypeKey: columnTypeKeyFor({ field, dialect, enums, exact: true }),
|
|
930
|
+
typeKey,
|
|
931
|
+
exactTypeKey: dialect === "postgres" ? columnTypeKeyFor({ field, dialect, enums, exact: true }) : typeKey,
|
|
899
932
|
pinsExactType: dialect === "postgres" && declaresExactColumnType({ field }),
|
|
900
933
|
nullable: field.isOptional,
|
|
901
934
|
defaultKey: defaultFingerprintFor({ field, dialect })
|
|
902
935
|
});
|
|
903
936
|
}
|
|
904
|
-
const pk = normalizePk({ model });
|
|
905
|
-
const indexes = normalizeIndexes({ model, table, dialect });
|
|
906
|
-
const fks = normalizeFks({ model, table,
|
|
937
|
+
const pk = normalizePk({ model, lookup });
|
|
938
|
+
const indexes = normalizeIndexes({ model, table, dialect, lookup });
|
|
939
|
+
const fks = normalizeFks({ model, table, lookup, dialect });
|
|
907
940
|
const checks = new Map;
|
|
908
941
|
for (const check of model.checks ?? [])
|
|
909
942
|
checks.set(check.name, check.expression);
|
|
910
943
|
return { model, table, ...model.namespace === undefined ? {} : { namespace: model.namespace }, columns, pk, indexes, fks, checks };
|
|
911
944
|
}
|
|
912
945
|
function normalizePk(params) {
|
|
913
|
-
const { model } = params;
|
|
946
|
+
const { model, lookup } = params;
|
|
914
947
|
if (model.primaryKey !== undefined && model.primaryKey.fields.length > 0) {
|
|
915
|
-
return model.primaryKey.fields.map((name) =>
|
|
948
|
+
return model.primaryKey.fields.map((name) => lookupFieldName({ lookup, model, name }));
|
|
916
949
|
}
|
|
917
950
|
return model.fields.filter((field) => field.isId).map((field) => field.dbName ?? field.name);
|
|
918
951
|
}
|
|
919
952
|
function normalizeIndexes(params) {
|
|
920
|
-
const { model, table, dialect } = params;
|
|
953
|
+
const { model, table, dialect, lookup } = params;
|
|
921
954
|
const result = new Map;
|
|
922
955
|
const add = (index) => {
|
|
923
956
|
const unique = index.kind === "unique";
|
|
924
957
|
const kind = unique ? "btree" : index.kind;
|
|
925
958
|
const columns = index.fields.map((field) => {
|
|
926
|
-
const fieldIR =
|
|
959
|
+
const fieldIR = lookup.fields.get(model)?.get(field.name);
|
|
927
960
|
const decimal = fieldIR !== undefined && fieldIR.type === "Decimal" && fieldIR.isList !== true;
|
|
928
961
|
return {
|
|
929
|
-
name:
|
|
962
|
+
name: fieldIR?.dbName ?? fieldIR?.name ?? field.name,
|
|
930
963
|
desc: field.sort === "desc",
|
|
931
964
|
...decimal ? { decimal: true } : {},
|
|
932
965
|
...field.operatorClass === undefined ? {} : { operatorClass: field.operatorClass }
|
|
@@ -960,8 +993,7 @@ function normalizeIndexes(params) {
|
|
|
960
993
|
}
|
|
961
994
|
function indexComparisonKey(params) {
|
|
962
995
|
const { unique, kind, columns, where, nullsNotDistinct } = params;
|
|
963
|
-
|
|
964
|
-
return `${unique ? "u" : "i"}${nullsNotDistinct === true ? "n" : ""}:${kind}:${cols}:${normalizeWhere({ where })}`;
|
|
996
|
+
return JSON.stringify([unique, nullsNotDistinct === true, kind, columns.map((column) => [column.name, column.desc === true, column.operatorClass === undefined ? null : [column.operatorClass.namespace, column.operatorClass.name]]), normalizeWhere({ where })]);
|
|
965
997
|
}
|
|
966
998
|
function normalizeWhere(params) {
|
|
967
999
|
const { where } = params;
|
|
@@ -1123,22 +1155,22 @@ function parsePrimaryExpression(params) {
|
|
|
1123
1155
|
return { text: parts.join(" "), at, kind: "atom" };
|
|
1124
1156
|
}
|
|
1125
1157
|
function normalizeFks(params) {
|
|
1126
|
-
const { model, table,
|
|
1158
|
+
const { model, table, lookup } = params;
|
|
1127
1159
|
const result = new Map;
|
|
1128
1160
|
for (const relation of model.relations) {
|
|
1129
1161
|
if (relation.fields === undefined || relation.fields.length === 0)
|
|
1130
1162
|
continue;
|
|
1131
|
-
const target =
|
|
1163
|
+
const target = lookup.models.get(relation.targetModel);
|
|
1132
1164
|
if (target !== undefined && target.isView)
|
|
1133
1165
|
continue;
|
|
1134
1166
|
const refTable = target === undefined ? relation.targetModel : target.dbName ?? target.name;
|
|
1135
|
-
const columns = relation.fields.map((name) =>
|
|
1136
|
-
const refColumns = (relation.references ?? []).map((name) => target === undefined ? name :
|
|
1167
|
+
const columns = relation.fields.map((name) => lookupFieldName({ lookup, model, name }));
|
|
1168
|
+
const refColumns = (relation.references ?? []).map((name) => target === undefined ? name : lookupFieldName({ lookup, model: target, name }));
|
|
1137
1169
|
const required = relation.fields.every((name) => {
|
|
1138
|
-
const field =
|
|
1170
|
+
const field = lookup.fields.get(model)?.get(name);
|
|
1139
1171
|
return field === undefined ? true : !field.isOptional;
|
|
1140
1172
|
});
|
|
1141
|
-
const key =
|
|
1173
|
+
const key = JSON.stringify([columns, physicalNameKey({ name: refTable, namespace: target?.namespace, dialect: params.dialect }), refColumns]);
|
|
1142
1174
|
if (result.has(key))
|
|
1143
1175
|
continue;
|
|
1144
1176
|
result.set(key, {
|
|
@@ -1155,17 +1187,17 @@ function normalizeFks(params) {
|
|
|
1155
1187
|
return result;
|
|
1156
1188
|
}
|
|
1157
1189
|
function normalizeJoinTable(params) {
|
|
1158
|
-
const { pair,
|
|
1190
|
+
const { pair, lookup, dialect } = params;
|
|
1159
1191
|
return {
|
|
1160
1192
|
table: pair.tableName,
|
|
1161
1193
|
...pair.namespace === undefined ? {} : { namespace: pair.namespace },
|
|
1162
|
-
a: joinTableEnd({ modelName: pair.modelA,
|
|
1163
|
-
b: joinTableEnd({ modelName: pair.modelB,
|
|
1194
|
+
a: joinTableEnd({ modelName: pair.modelA, lookup, dialect, joinTable: pair.tableName }),
|
|
1195
|
+
b: joinTableEnd({ modelName: pair.modelB, lookup, dialect, joinTable: pair.tableName })
|
|
1164
1196
|
};
|
|
1165
1197
|
}
|
|
1166
1198
|
function joinTableEnd(params) {
|
|
1167
|
-
const { modelName,
|
|
1168
|
-
const model =
|
|
1199
|
+
const { modelName, lookup, joinTable } = params;
|
|
1200
|
+
const model = lookup.models.get(modelName);
|
|
1169
1201
|
if (model === undefined) {
|
|
1170
1202
|
throw new VibeError5({
|
|
1171
1203
|
code: "VIBE_MIGRATION",
|
|
@@ -1173,7 +1205,7 @@ function joinTableEnd(params) {
|
|
|
1173
1205
|
meta: { joinTable, model: modelName }
|
|
1174
1206
|
});
|
|
1175
1207
|
}
|
|
1176
|
-
const pkFields = model.primaryKey !== undefined && model.primaryKey.fields.length > 0 ? model.primaryKey.fields.map((name) =>
|
|
1208
|
+
const pkFields = model.primaryKey !== undefined && model.primaryKey.fields.length > 0 ? model.primaryKey.fields.map((name) => lookup.fields.get(model)?.get(name)) : model.fields.filter((field) => field.isId);
|
|
1177
1209
|
const pkField = pkFields[0];
|
|
1178
1210
|
if (pkFields.length !== 1 || pkField === undefined) {
|
|
1179
1211
|
throw new VibeError5({
|
|
@@ -1186,7 +1218,7 @@ function joinTableEnd(params) {
|
|
|
1186
1218
|
table: model.dbName ?? model.name,
|
|
1187
1219
|
...model.namespace === undefined ? {} : { namespace: model.namespace },
|
|
1188
1220
|
column: pkField.dbName ?? pkField.name,
|
|
1189
|
-
field: physicalEnumField({ field: pkField,
|
|
1221
|
+
field: physicalEnumField({ field: pkField, lookup, dialect: params.dialect, table: model.dbName ?? model.name })
|
|
1190
1222
|
};
|
|
1191
1223
|
}
|
|
1192
1224
|
function physicalNameKey(params) {
|
|
@@ -1202,11 +1234,6 @@ function schemaNamespaces(params) {
|
|
|
1202
1234
|
...(params.schema.migrationObjects ?? []).flatMap((object) => object.identity.kind === "namespace" ? [object.identity.name] : [])
|
|
1203
1235
|
])];
|
|
1204
1236
|
}
|
|
1205
|
-
function fieldDbName(params) {
|
|
1206
|
-
const { model, name } = params;
|
|
1207
|
-
const field = model.fields.find((candidate) => candidate.name === name);
|
|
1208
|
-
return field === undefined ? name : field.dbName ?? field.name;
|
|
1209
|
-
}
|
|
1210
1237
|
|
|
1211
1238
|
// packages/migrate/src/object-ownership.ts
|
|
1212
1239
|
import { VibeError as VibeError6, defaultPrimaryKeyName, enumStorageOf as enumStorageOf2, implicitJoinTableObjectNames, postgresGeneratedObjectName, qualifiedSqlName as qualifiedSqlName4, sqlObjectKey as sqlObjectKey2 } from "@vibeorm/schema";
|
|
@@ -1311,7 +1338,10 @@ function removalIdentity(params) {
|
|
|
1311
1338
|
}
|
|
1312
1339
|
}
|
|
1313
1340
|
function schemaObjectIdentities(params) {
|
|
1314
|
-
|
|
1341
|
+
return schemaObjectIdentitiesFromNormalized({ ...params, normalized: normalizeSchema({ schema: params.schema, dialect: params.dialect }) });
|
|
1342
|
+
}
|
|
1343
|
+
function schemaObjectIdentitiesFromNormalized(params) {
|
|
1344
|
+
const { normalized } = params;
|
|
1315
1345
|
const result = [];
|
|
1316
1346
|
if (params.dialect === "postgres") {
|
|
1317
1347
|
for (const namespace of schemaNamespaces({ schema: params.schema }))
|
|
@@ -1426,7 +1456,7 @@ function assertRemovalAuthorization(params) {
|
|
|
1426
1456
|
}
|
|
1427
1457
|
|
|
1428
1458
|
// packages/migrate/src/inventory.ts
|
|
1429
|
-
import { VibeError as
|
|
1459
|
+
import { VibeError as VibeError18, defaultForeignKeyName as defaultForeignKeyName2, defaultPrimaryKeyName as defaultPrimaryKeyName2, isSqlObjectIdentity as isSqlObjectIdentity2, sqlObjectKey as sqlObjectKey6 } from "@vibeorm/schema";
|
|
1430
1460
|
import { getSqlDialect as getSqlDialect3, renderMigrationInventory, renderSqliteColumnInventory as renderSqliteColumnInventory2, renderMysqlObjectMembers as renderMysqlObjectMembers2, sqliteTableConstraints as sqliteTableConstraints3, renderSqliteForeignKeyInventory } from "@vibeorm/sql";
|
|
1431
1461
|
|
|
1432
1462
|
// packages/migrate/src/external-objects.ts
|
|
@@ -1796,7 +1826,7 @@ async function verifyExclusions(params) {
|
|
|
1796
1826
|
|
|
1797
1827
|
// packages/migrate/src/inspect-objects.ts
|
|
1798
1828
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1799
|
-
import { VibeError as
|
|
1829
|
+
import { VibeError as VibeError17, sqlObjectKey as sqlObjectKey5, sqlObjectNamespace as sqlObjectNamespace2, DIALECT_CAPABILITIES as DIALECT_CAPABILITIES2, qualifiedSqlName as qualifiedSqlName8 } from "@vibeorm/schema";
|
|
1800
1830
|
import { getSqlDialect as getSqlDialect2, renderMigrationObjectBatch, renderMigrationObjectQuery as renderMigrationObjectQuery2, renderCheckProbe, renderMigrationNamespace, renderMigrationDependencies, renderMysqlObjectMembers, renderMysqlCheckProbe, sqliteColumnClauses as sqliteColumnClauses2, renderMigrationVisibility, sqliteTableConstraints as sqliteTableConstraints2, renderSqliteColumnInventory } from "@vibeorm/sql";
|
|
1801
1831
|
|
|
1802
1832
|
// packages/migrate/src/introspect/postgres.ts
|
|
@@ -1994,6 +2024,20 @@ function lowerFirst(value) {
|
|
|
1994
2024
|
return value.charAt(0).toLowerCase() + value.slice(1);
|
|
1995
2025
|
}
|
|
1996
2026
|
|
|
2027
|
+
// packages/migrate/src/introspect/group.ts
|
|
2028
|
+
function groupCatalogRows(params) {
|
|
2029
|
+
const groups = new Map;
|
|
2030
|
+
for (const row of params.rows) {
|
|
2031
|
+
const key = params.key(row);
|
|
2032
|
+
const group = groups.get(key);
|
|
2033
|
+
if (group === undefined)
|
|
2034
|
+
groups.set(key, [row]);
|
|
2035
|
+
else
|
|
2036
|
+
group.push(row);
|
|
2037
|
+
}
|
|
2038
|
+
return groups;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
1997
2041
|
// packages/migrate/src/postgres-type-identity.ts
|
|
1998
2042
|
import { VibeError as VibeError10, qualifiedSqlName as qualifiedSqlName6 } from "@vibeorm/schema";
|
|
1999
2043
|
import { postgresDialect as postgresDialect3, renderPostgresTypeIdentity } from "@vibeorm/sql";
|
|
@@ -2395,10 +2439,15 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2395
2439
|
read("checks"),
|
|
2396
2440
|
Promise.all(namespaces.map(async (namespace) => (await executor({ text: renderExclusionCatalog2({ dialect: postgresDialect6, namespace }) })).map((row) => ({ ...row, table_name: physicalNameKey({ namespace, name: String(row.table_name) }) })))).then((rows) => rows.flat())
|
|
2397
2441
|
]);
|
|
2398
|
-
const keysQuery = renderPostgresIndexKeys({ dialect: postgresDialect6 });
|
|
2442
|
+
const keysQuery = renderPostgresIndexKeys({ dialect: postgresDialect6, namespaces });
|
|
2399
2443
|
const indexKeys = await executor({ text: keysQuery.text, values: [...keysQuery.values] });
|
|
2400
2444
|
if (indexKeys.some((row) => typeof row.table_namespace !== "string" || typeof row.table_name !== "string" || typeof row.index_name !== "string" || typeof row.method !== "string" || typeof row.unique !== "boolean" || typeof row.descending !== "boolean" || typeof row.exact_default !== "boolean" && row.column_name !== null || typeof row.operator_namespace !== "string" || typeof row.operator_name !== "string" || !Number.isSafeInteger(Number(row.ordinal)) || Number(row.ordinal) < 1))
|
|
2401
2445
|
throw new VibeError13({ code: "VIBE_MIGRATION", message: "PostgreSQL index-key identity is incomplete", meta: { reason: "index-inspection-unavailable" } });
|
|
2446
|
+
const keysByIndex = groupCatalogRows({ rows: indexKeys, key: (row) => JSON.stringify([physicalNameKey({ namespace: String(row.table_namespace), name: String(row.table_name) }), row.index_name]) });
|
|
2447
|
+
const catalogColumns = new Map(columnRows.map((row) => [JSON.stringify([row.table_name, row.column_name]), row]));
|
|
2448
|
+
const foreignKeysByTable = groupCatalogRows({ rows: fkRows, key: (row) => String(row.table_name) });
|
|
2449
|
+
const catalogIndexesByTable = groupCatalogRows({ rows: indexRows, key: (row) => String(row.table_name) });
|
|
2450
|
+
const exclusionsByTable = groupCatalogRows({ rows: exclusionRows, key: (row) => String(row.table_name) });
|
|
2402
2451
|
const tables = tableRows.map((row) => row.table_name);
|
|
2403
2452
|
const views = viewRows.map((row) => row.table_name);
|
|
2404
2453
|
const columns = columnRows.map((row) => ({
|
|
@@ -2416,6 +2465,8 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2416
2465
|
numericScale: toNumberOrNull(row.numeric_scale),
|
|
2417
2466
|
fullType: row.full_type ?? null
|
|
2418
2467
|
}));
|
|
2468
|
+
const columnsByTable = groupCatalogRows({ rows: columns, key: (column) => column.table });
|
|
2469
|
+
const columnsByIdentity = new Map(columns.map((column) => [JSON.stringify([column.table, column.name]), column]));
|
|
2419
2470
|
const enums = [];
|
|
2420
2471
|
{
|
|
2421
2472
|
const valuesByName = new Map;
|
|
@@ -2456,6 +2507,7 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2456
2507
|
entry.columns.push(columnName);
|
|
2457
2508
|
}
|
|
2458
2509
|
}
|
|
2510
|
+
const uniquesByTable = groupCatalogRows({ rows: uniquesByConstraint.values(), key: (unique) => unique.table });
|
|
2459
2511
|
const checksByTable = new Map;
|
|
2460
2512
|
for (const row of checkRows) {
|
|
2461
2513
|
const table = row.table_name;
|
|
@@ -2490,7 +2542,7 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2490
2542
|
const fkGroups = [...fkGroupsByName.values()].map((group) => ({
|
|
2491
2543
|
...group,
|
|
2492
2544
|
required: group.columns.every((columnName) => {
|
|
2493
|
-
const column =
|
|
2545
|
+
const column = columnsByIdentity.get(JSON.stringify([group.table, columnName]));
|
|
2494
2546
|
return column === undefined ? true : !column.nullable;
|
|
2495
2547
|
})
|
|
2496
2548
|
}));
|
|
@@ -2498,7 +2550,7 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2498
2550
|
for (const row of indexRows) {
|
|
2499
2551
|
if (row.backing_constraint_type === "x")
|
|
2500
2552
|
continue;
|
|
2501
|
-
const operands =
|
|
2553
|
+
const operands = keysByIndex.get(JSON.stringify([row.table_name, row.index_name])) ?? [];
|
|
2502
2554
|
if (row.plain_columns === true && operands.length !== Number(row.key_count))
|
|
2503
2555
|
throw new VibeError13({ code: "VIBE_MIGRATION", message: "Plain index keys could not be read completely; refusing name-only operator-class introspection", meta: { reason: "index-inspection-unavailable", table: row.table_name, index: row.index_name } });
|
|
2504
2556
|
if (operands.length > 0) {
|
|
@@ -2523,22 +2575,24 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2523
2575
|
indexes.push(parsed);
|
|
2524
2576
|
}
|
|
2525
2577
|
}
|
|
2578
|
+
const indexesByTable = groupCatalogRows({ rows: indexes, key: (index) => index.table });
|
|
2579
|
+
const fkGroupsByTable = groupCatalogRows({ rows: fkGroups, key: (group) => group.table });
|
|
2526
2580
|
const joinTables = new Map;
|
|
2527
2581
|
for (const table of tables) {
|
|
2528
2582
|
const physicalTable = physicalNames.get(table);
|
|
2529
2583
|
if (!physicalTable.name.startsWith("_"))
|
|
2530
2584
|
continue;
|
|
2531
|
-
const tableFks =
|
|
2532
|
-
const tableIndexes =
|
|
2533
|
-
if (pkByTable.has(table) || (checksByTable.get(table)?.length ?? 0) > 0 ||
|
|
2585
|
+
const tableFks = foreignKeysByTable.get(table) ?? [];
|
|
2586
|
+
const tableIndexes = catalogIndexesByTable.get(table) ?? [];
|
|
2587
|
+
if (pkByTable.has(table) || (checksByTable.get(table)?.length ?? 0) > 0 || exclusionsByTable.has(table))
|
|
2534
2588
|
continue;
|
|
2535
2589
|
if (!tableFks.every((row) => row.validated === true && row.deferrable === false && row.triggers_enabled === true))
|
|
2536
2590
|
continue;
|
|
2537
2591
|
if (tableIndexes.length !== 2 || !tableIndexes.every((row) => row.valid === true && row.ready === true && row.immediate === true && row.plain_columns === true && typeof row.key_count === "number" && row.key_count === row.attribute_count))
|
|
2538
2592
|
continue;
|
|
2539
2593
|
if (tableFks.some((row) => {
|
|
2540
|
-
const source =
|
|
2541
|
-
const target =
|
|
2594
|
+
const source = catalogColumns.get(JSON.stringify([table, row.column_name]));
|
|
2595
|
+
const target = catalogColumns.get(JSON.stringify([row.foreign_table_name, row.foreign_column_name]));
|
|
2542
2596
|
return source === undefined || target === undefined || typeof source.full_type !== "string" || source.full_type.length === 0 || source.full_type !== target.full_type || source.column_default !== null || source.is_identity !== "NO" || source.is_generated !== "NEVER" || source.is_nullable !== "NO" || source.collation_name === undefined || source.collation_name !== target.collation_name;
|
|
2543
2597
|
}))
|
|
2544
2598
|
continue;
|
|
@@ -2546,9 +2600,9 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2546
2600
|
continue;
|
|
2547
2601
|
const detected = detectJoinTable({
|
|
2548
2602
|
table: physicalTable.name,
|
|
2549
|
-
columnNames:
|
|
2550
|
-
fkGroups:
|
|
2551
|
-
indexes:
|
|
2603
|
+
columnNames: (columnsByTable.get(table) ?? []).map((column) => column.name),
|
|
2604
|
+
fkGroups: (fkGroupsByTable.get(table) ?? []).map((group) => ({ ...group, table: physicalTable.name, refTable: physicalNames.get(group.refTable)?.name ?? group.refTable })),
|
|
2605
|
+
indexes: (indexesByTable.get(table) ?? []).map((index) => ({ ...index, table: physicalTable.name }))
|
|
2552
2606
|
});
|
|
2553
2607
|
if (detected !== null)
|
|
2554
2608
|
joinTables.set(table, { ...detected, ...physicalTable.namespace === "public" && namespaces.length === 1 ? {} : { relationName: detected.relationName ?? physicalTable.name.slice(1) }, tableA: physicalNameKey({ namespace: physicalTable.namespace, name: detected.tableA }), tableB: physicalNameKey({ namespace: physicalTable.namespace, name: detected.tableB }) });
|
|
@@ -2561,14 +2615,14 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2561
2615
|
table,
|
|
2562
2616
|
physicalTable: physicalNames.get(table).name,
|
|
2563
2617
|
isView: false,
|
|
2564
|
-
columns,
|
|
2618
|
+
columns: columnsByTable.get(table) ?? [],
|
|
2565
2619
|
pkByTable,
|
|
2566
2620
|
pkNames,
|
|
2567
|
-
|
|
2621
|
+
uniques: uniquesByTable.get(table) ?? [],
|
|
2568
2622
|
constraintNames,
|
|
2569
|
-
indexes,
|
|
2623
|
+
indexes: indexesByTable.get(table) ?? [],
|
|
2570
2624
|
enumNames,
|
|
2571
|
-
exclusions:
|
|
2625
|
+
exclusions: (exclusionsByTable.get(table) ?? []).map((row) => ({ name: String(row.constraint_name), definition: String(row.definition), requiredExtensions: [], catalogDefinition: true })),
|
|
2572
2626
|
checks: checksByTable.get(table) ?? []
|
|
2573
2627
|
}));
|
|
2574
2628
|
}
|
|
@@ -2577,10 +2631,10 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2577
2631
|
table: view,
|
|
2578
2632
|
physicalTable: physicalNames.get(view).name,
|
|
2579
2633
|
isView: true,
|
|
2580
|
-
columns,
|
|
2634
|
+
columns: columnsByTable.get(view) ?? [],
|
|
2581
2635
|
pkByTable,
|
|
2582
2636
|
pkNames,
|
|
2583
|
-
|
|
2637
|
+
uniques: uniquesByTable.get(view) ?? [],
|
|
2584
2638
|
constraintNames,
|
|
2585
2639
|
indexes: [],
|
|
2586
2640
|
enumNames,
|
|
@@ -2600,14 +2654,16 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2600
2654
|
logicalNames.add(name);
|
|
2601
2655
|
return name;
|
|
2602
2656
|
};
|
|
2657
|
+
const modelBasenames = groupCatalogRows({ rows: physicalNames.values(), key: (entry) => entry.name });
|
|
2658
|
+
const enumBasenames = groupCatalogRows({ rows: enumIdentities.values(), key: (entry) => entry.name });
|
|
2603
2659
|
const modelNames = new Map(models.map((model) => {
|
|
2604
2660
|
const identity = physicalNames.get(model.name);
|
|
2605
|
-
const duplicates =
|
|
2661
|
+
const duplicates = (modelBasenames.get(identity.name)?.length ?? 0) > 1;
|
|
2606
2662
|
return [model.name, logicalName({ key: model.name, name: identity.name, duplicates })];
|
|
2607
2663
|
}));
|
|
2608
2664
|
const enumLogicalNames = new Map(enums.map((entry) => {
|
|
2609
2665
|
const identity = enumIdentities.get(entry.name);
|
|
2610
|
-
const duplicates =
|
|
2666
|
+
const duplicates = (enumBasenames.get(identity.name)?.length ?? 0) > 1;
|
|
2611
2667
|
return [entry.name, logicalName({ key: entry.name, name: identity.name, duplicates })];
|
|
2612
2668
|
}));
|
|
2613
2669
|
const qualifiedModels = models.map((model) => {
|
|
@@ -2645,20 +2701,18 @@ async function introspectPhysicalPostgres(params) {
|
|
|
2645
2701
|
return resolved.schema;
|
|
2646
2702
|
}
|
|
2647
2703
|
function buildModel(params) {
|
|
2648
|
-
const { table, isView, columns, pkByTable,
|
|
2649
|
-
const tableColumns = columns
|
|
2704
|
+
const { table, isView, columns, pkByTable, uniques, constraintNames, indexes, enumNames } = params;
|
|
2705
|
+
const tableColumns = columns;
|
|
2650
2706
|
const pk = pkByTable.get(table) ?? [];
|
|
2651
2707
|
const uniqueSingles = new Set;
|
|
2652
2708
|
const multiUniques = [];
|
|
2653
|
-
for (const unique of
|
|
2709
|
+
for (const unique of uniques) {
|
|
2654
2710
|
const name = unique.name;
|
|
2655
|
-
if (unique.table !== table)
|
|
2656
|
-
continue;
|
|
2657
2711
|
if (unique.columns.length === 1)
|
|
2658
2712
|
uniqueSingles.add(unique.columns[0]);
|
|
2659
2713
|
multiUniques.push({ name, columns: unique.columns });
|
|
2660
2714
|
}
|
|
2661
|
-
const tableIndexes = indexes.filter((index) =>
|
|
2715
|
+
const tableIndexes = indexes.filter((index) => !constraintNames.has(JSON.stringify([table, index.name])));
|
|
2662
2716
|
for (const index of tableIndexes) {
|
|
2663
2717
|
if (!index.unique)
|
|
2664
2718
|
continue;
|
|
@@ -2943,7 +2997,7 @@ function toNumberOrNull(value) {
|
|
|
2943
2997
|
}
|
|
2944
2998
|
|
|
2945
2999
|
// packages/migrate/src/introspect/mysql.ts
|
|
2946
|
-
import { exactIntegerLiteral as exactIntegerLiteral2 } from "@vibeorm/schema";
|
|
3000
|
+
import { VibeError as VibeError14, exactIntegerLiteral as exactIntegerLiteral2 } from "@vibeorm/schema";
|
|
2947
3001
|
import { mysqlDialect as mysqlDialect2 } from "@vibeorm/sql";
|
|
2948
3002
|
|
|
2949
3003
|
// packages/migrate/src/journal-table.ts
|
|
@@ -3072,6 +3126,12 @@ async function introspectMysql(params) {
|
|
|
3072
3126
|
collation: catalogField({ row, name: "collation_name" }) ?? null,
|
|
3073
3127
|
extra: (catalogField({ row, name: "extra" }) ?? "").toLowerCase()
|
|
3074
3128
|
}));
|
|
3129
|
+
const columnsByTable = groupCatalogRows({ rows: columns, key: (column) => column.table });
|
|
3130
|
+
const columnsByIdentity = new Map(columns.map((column) => [JSON.stringify([column.table, column.name]), column]));
|
|
3131
|
+
const catalogColumns = new Map(columnRows.map((row) => [JSON.stringify([catalogField({ row, name: "table_name" }), catalogField({ row, name: "column_name" })]), row]));
|
|
3132
|
+
const statisticsByTable = groupCatalogRows({ rows: statisticRows, key: (row) => String(catalogField({ row, name: "table_name" })) });
|
|
3133
|
+
const foreignKeysByTable = groupCatalogRows({ rows: fkRows, key: (row) => String(catalogField({ row, name: "table_name" })) });
|
|
3134
|
+
const enginesByTable = new Map(tableRows.map((row) => [catalogField({ row, name: "table_name" }), catalogField({ row, name: "engine" })]));
|
|
3075
3135
|
const rulesByConstraint = new Map;
|
|
3076
3136
|
for (const row of refRows) {
|
|
3077
3137
|
rulesByConstraint.set(catalogField({ row, name: "constraint_name" }), {
|
|
@@ -3083,6 +3143,15 @@ async function introspectMysql(params) {
|
|
|
3083
3143
|
for (const row of fkRows) {
|
|
3084
3144
|
const constraintName = catalogField({ row, name: "constraint_name" });
|
|
3085
3145
|
const table = catalogField({ row, name: "table_name" });
|
|
3146
|
+
const tableSchema = catalogField({ row, name: "table_schema" });
|
|
3147
|
+
const referencedSchema = catalogField({ row, name: "referenced_table_schema" });
|
|
3148
|
+
if (typeof tableSchema === "string" && typeof referencedSchema === "string" && tableSchema !== referencedSchema) {
|
|
3149
|
+
throw new VibeError14({
|
|
3150
|
+
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
3151
|
+
message: `MySQL cross-database foreign key "${constraintName}" from "${tableSchema}.${table}" to "${referencedSchema}" cannot be introspected without losing its database identity`,
|
|
3152
|
+
meta: { dialect: "mysql", feature: "cross-database foreign keys", constraint: constraintName, table, tableSchema, referencedSchema }
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3086
3155
|
const key = `${table}\x00${constraintName}`;
|
|
3087
3156
|
const existing = groupsByKey.get(key);
|
|
3088
3157
|
if (existing === undefined) {
|
|
@@ -3105,17 +3174,11 @@ async function introspectMysql(params) {
|
|
|
3105
3174
|
onDelete: rules?.onDelete ?? "NoAction",
|
|
3106
3175
|
onUpdate: rules?.onUpdate ?? "NoAction",
|
|
3107
3176
|
required: group.columns.every((columnName) => {
|
|
3108
|
-
const column =
|
|
3177
|
+
const column = columnsByIdentity.get(JSON.stringify([group.table, columnName]));
|
|
3109
3178
|
return column === undefined ? true : !column.nullable;
|
|
3110
3179
|
})
|
|
3111
3180
|
};
|
|
3112
3181
|
});
|
|
3113
|
-
const fkNamesByTable = new Map;
|
|
3114
|
-
for (const group of fkGroups) {
|
|
3115
|
-
const set = fkNamesByTable.get(group.table) ?? new Set;
|
|
3116
|
-
set.add(group.constraintName);
|
|
3117
|
-
fkNamesByTable.set(group.table, set);
|
|
3118
|
-
}
|
|
3119
3182
|
const pkByTable = new Map;
|
|
3120
3183
|
const indexes = [];
|
|
3121
3184
|
{
|
|
@@ -3124,6 +3187,12 @@ async function introspectMysql(params) {
|
|
|
3124
3187
|
const table = catalogField({ row, name: "table_name" });
|
|
3125
3188
|
const name = catalogField({ row, name: "index_name" });
|
|
3126
3189
|
const columnName = catalogField({ row, name: "column_name" });
|
|
3190
|
+
const type = catalogField({ row, name: "index_type" });
|
|
3191
|
+
const unique = Number(catalogField({ row, name: "non_unique" })) === 0;
|
|
3192
|
+
const kind = typeof type === "string" ? type.toLowerCase() : "btree";
|
|
3193
|
+
if (catalogField({ row, name: "sub_part" }) != null || catalogField({ row, name: "expression" }) != null || catalogField({ row, name: "is_visible" }) === "NO" || typeof columnName !== "string" || kind !== "btree" && (kind !== "fulltext" || unique)) {
|
|
3194
|
+
throw new VibeError14({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `MySQL index "${table}.${name}" cannot be introspected without losing its physical shape`, meta: { dialect: "mysql", feature: "index introspection", table, index: name } });
|
|
3195
|
+
}
|
|
3127
3196
|
if (name === "PRIMARY") {
|
|
3128
3197
|
const list = pkByTable.get(table);
|
|
3129
3198
|
if (list === undefined)
|
|
@@ -3132,8 +3201,6 @@ async function introspectMysql(params) {
|
|
|
3132
3201
|
list.push(columnName);
|
|
3133
3202
|
continue;
|
|
3134
3203
|
}
|
|
3135
|
-
if (fkNamesByTable.get(table)?.has(name) === true)
|
|
3136
|
-
continue;
|
|
3137
3204
|
const key = `${table}\x00${name}`;
|
|
3138
3205
|
const entry = byKey.get(key);
|
|
3139
3206
|
const column = {
|
|
@@ -3144,7 +3211,8 @@ async function introspectMysql(params) {
|
|
|
3144
3211
|
byKey.set(key, {
|
|
3145
3212
|
table,
|
|
3146
3213
|
name,
|
|
3147
|
-
unique
|
|
3214
|
+
unique,
|
|
3215
|
+
kind,
|
|
3148
3216
|
columns: [column]
|
|
3149
3217
|
});
|
|
3150
3218
|
} else {
|
|
@@ -3152,34 +3220,39 @@ async function introspectMysql(params) {
|
|
|
3152
3220
|
}
|
|
3153
3221
|
}
|
|
3154
3222
|
for (const entry of byKey.values()) {
|
|
3155
|
-
|
|
3223
|
+
const support = groupsByKey.get(`${entry.table}\x00${entry.name}`);
|
|
3224
|
+
if (!entry.unique && entry.kind === "btree" && support !== undefined && entry.columns.length === support.columns.length && entry.columns.every((column, index) => column.name === support.columns[index] && !column.desc))
|
|
3225
|
+
continue;
|
|
3226
|
+
indexes.push(entry);
|
|
3156
3227
|
}
|
|
3157
3228
|
}
|
|
3229
|
+
const indexesByTable = groupCatalogRows({ rows: indexes, key: (index) => index.table });
|
|
3230
|
+
const fkGroupsByTable = groupCatalogRows({ rows: fkGroups, key: (group) => group.table });
|
|
3158
3231
|
const enums = [];
|
|
3159
3232
|
const joinTables = new Map;
|
|
3160
3233
|
let foreignKeysEnabled;
|
|
3161
3234
|
for (const table of tables) {
|
|
3162
3235
|
if (!table.startsWith("_") || pkByTable.has(table) || (checksByTable.get(table)?.length ?? 0) > 0)
|
|
3163
3236
|
continue;
|
|
3164
|
-
const tableIndexes =
|
|
3237
|
+
const tableIndexes = statisticsByTable.get(table) ?? [];
|
|
3165
3238
|
if (new Set(tableIndexes.map((row) => catalogField({ row, name: "index_name" }))).size !== 2 || !tableIndexes.every((row) => catalogField({ row, name: "index_type" }) === "BTREE" && catalogField({ row, name: "is_visible" }) === "YES" && catalogField({ row, name: "sub_part" }) === null && catalogField({ row, name: "expression" }) === null && catalogField({ row, name: "collation" }) === "A" && Number.isInteger(Number(catalogField({ row, name: "seq_in_index" }))) && Number(catalogField({ row, name: "seq_in_index" })) > 0))
|
|
3166
3239
|
continue;
|
|
3167
|
-
const tableFks =
|
|
3240
|
+
const tableFks = foreignKeysByTable.get(table) ?? [];
|
|
3168
3241
|
if (tableFks.some((row) => {
|
|
3169
3242
|
const targetTable = catalogField({ row, name: "referenced_table_name" });
|
|
3170
|
-
const source =
|
|
3171
|
-
const target =
|
|
3243
|
+
const source = catalogColumns.get(JSON.stringify([table, catalogField({ row, name: "column_name" })]));
|
|
3244
|
+
const target = catalogColumns.get(JSON.stringify([targetTable, catalogField({ row, name: "referenced_column_name" })]));
|
|
3172
3245
|
if (source === undefined || target === undefined)
|
|
3173
3246
|
return true;
|
|
3174
3247
|
const type = catalogField({ row: source, name: "column_type" });
|
|
3175
|
-
return typeof type !== "string" || type.length === 0 || type !== catalogField({ row: target, name: "column_type" }) || catalogField({ row: source, name: "column_default" }) !== null || catalogField({ row: source, name: "extra" }) !== "" || catalogField({ row: source, name: "generation_expression" }) !== "" || catalogField({ row: source, name: "is_nullable" }) !== "NO" || catalogField({ row: source, name: "collation_name" }) === undefined || catalogField({ row: source, name: "collation_name" }) !== catalogField({ row: target, name: "collation_name" }) || catalogField({ row, name: "table_schema" }) === undefined || catalogField({ row, name: "table_schema" }) !== catalogField({ row, name: "referenced_table_schema" }) || ![table, targetTable].every((name) =>
|
|
3248
|
+
return typeof type !== "string" || type.length === 0 || type !== catalogField({ row: target, name: "column_type" }) || catalogField({ row: source, name: "column_default" }) !== null || catalogField({ row: source, name: "extra" }) !== "" || catalogField({ row: source, name: "generation_expression" }) !== "" || catalogField({ row: source, name: "is_nullable" }) !== "NO" || catalogField({ row: source, name: "collation_name" }) === undefined || catalogField({ row: source, name: "collation_name" }) !== catalogField({ row: target, name: "collation_name" }) || catalogField({ row, name: "table_schema" }) === undefined || catalogField({ row, name: "table_schema" }) !== catalogField({ row, name: "referenced_table_schema" }) || ![table, targetTable].every((name) => enginesByTable.get(name) === "InnoDB");
|
|
3176
3249
|
}))
|
|
3177
3250
|
continue;
|
|
3178
3251
|
const detected = detectJoinTable({
|
|
3179
3252
|
table,
|
|
3180
|
-
columnNames:
|
|
3181
|
-
fkGroups,
|
|
3182
|
-
indexes
|
|
3253
|
+
columnNames: (columnsByTable.get(table) ?? []).map((column) => column.name),
|
|
3254
|
+
fkGroups: fkGroupsByTable.get(table) ?? [],
|
|
3255
|
+
indexes: indexesByTable.get(table) ?? []
|
|
3183
3256
|
});
|
|
3184
3257
|
if (detected === null)
|
|
3185
3258
|
continue;
|
|
@@ -3191,7 +3264,7 @@ async function introspectMysql(params) {
|
|
|
3191
3264
|
continue;
|
|
3192
3265
|
const q = (name) => mysqlDialect2.quoteIdent({ name });
|
|
3193
3266
|
let valid = true;
|
|
3194
|
-
for (const fk of
|
|
3267
|
+
for (const fk of fkGroupsByTable.get(table) ?? []) {
|
|
3195
3268
|
const rows = await executor({ text: `SELECT 1 AS invalid FROM ${q(table)} child LEFT JOIN ${q(fk.refTable)} parent ON child.${q(fk.columns[0])} = parent.${q(fk.refColumns[0])} WHERE parent.${q(fk.refColumns[0])} IS NULL LIMIT 1`, values: [] });
|
|
3196
3269
|
if (rows.length > 0) {
|
|
3197
3270
|
valid = false;
|
|
@@ -3208,9 +3281,9 @@ async function introspectMysql(params) {
|
|
|
3208
3281
|
models.push(buildModel2({
|
|
3209
3282
|
table,
|
|
3210
3283
|
isView: false,
|
|
3211
|
-
columns,
|
|
3284
|
+
columns: columnsByTable.get(table) ?? [],
|
|
3212
3285
|
pkByTable,
|
|
3213
|
-
indexes,
|
|
3286
|
+
indexes: indexesByTable.get(table) ?? [],
|
|
3214
3287
|
enums,
|
|
3215
3288
|
checks: checksByTable.get(table) ?? []
|
|
3216
3289
|
}));
|
|
@@ -3219,7 +3292,7 @@ async function introspectMysql(params) {
|
|
|
3219
3292
|
models.push(buildModel2({
|
|
3220
3293
|
table: view,
|
|
3221
3294
|
isView: true,
|
|
3222
|
-
columns,
|
|
3295
|
+
columns: columnsByTable.get(view) ?? [],
|
|
3223
3296
|
pkByTable: new Map,
|
|
3224
3297
|
indexes: [],
|
|
3225
3298
|
enums,
|
|
@@ -3237,9 +3310,9 @@ async function introspectMysql(params) {
|
|
|
3237
3310
|
}
|
|
3238
3311
|
function buildModel2(params) {
|
|
3239
3312
|
const { table, isView, columns, pkByTable, indexes, enums } = params;
|
|
3240
|
-
const tableColumns = columns
|
|
3313
|
+
const tableColumns = columns;
|
|
3241
3314
|
const pk = pkByTable.get(table) ?? [];
|
|
3242
|
-
const tableIndexes = indexes
|
|
3315
|
+
const tableIndexes = indexes;
|
|
3243
3316
|
const uniqueSingles = new Set;
|
|
3244
3317
|
for (const index of tableIndexes) {
|
|
3245
3318
|
if (index.unique && index.columns.length === 1) {
|
|
@@ -3270,7 +3343,7 @@ function buildModel2(params) {
|
|
|
3270
3343
|
continue;
|
|
3271
3344
|
modelIndexes.push({
|
|
3272
3345
|
name: index.name,
|
|
3273
|
-
kind: index.unique ? "unique" :
|
|
3346
|
+
kind: index.unique ? "unique" : index.kind,
|
|
3274
3347
|
fields: index.columns.map((column) => ({
|
|
3275
3348
|
name: column.name,
|
|
3276
3349
|
...column.desc ? { sort: "desc" } : {}
|
|
@@ -3802,7 +3875,7 @@ function q(name) {
|
|
|
3802
3875
|
}
|
|
3803
3876
|
|
|
3804
3877
|
// packages/migrate/src/execute.ts
|
|
3805
|
-
import { VibeError as
|
|
3878
|
+
import { VibeError as VibeError15 } from "@vibeorm/schema";
|
|
3806
3879
|
var SELF_MANAGED_MARKER = "PRAGMA foreign_keys=OFF";
|
|
3807
3880
|
var FK_CHECK_PREFIX = "PRAGMA foreign_key_check";
|
|
3808
3881
|
function usesSelfManagedTransactions(params) {
|
|
@@ -3843,7 +3916,7 @@ function assertSelfManagedResumeSafe(params) {
|
|
|
3843
3916
|
const completedIndex = params.direction === "up" ? params.start + 1 : -(params.start + 2);
|
|
3844
3917
|
const rewind = selfManagedResumeStart({ sql: params.sql, start: params.start });
|
|
3845
3918
|
const retryIndex = params.direction === "up" ? rewind : -(rewind + 1);
|
|
3846
|
-
throw new
|
|
3919
|
+
throw new VibeError15({
|
|
3847
3920
|
code: "VIBE_MIGRATION",
|
|
3848
3921
|
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.`,
|
|
3849
3922
|
meta: { reason: "uncertain-statement", migration: params.migration, statementIndex: params.start, direction: params.direction, dialect: "sqlite" }
|
|
@@ -3872,7 +3945,7 @@ function unsupportedScript(params) {
|
|
|
3872
3945
|
const { migration, direction, index, problem, found } = params;
|
|
3873
3946
|
const subject = direction === "down" ? `the down script of migration "${migration}"` : `migration "${migration}"`;
|
|
3874
3947
|
const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${params.statements}`;
|
|
3875
|
-
return new
|
|
3948
|
+
return new VibeError15({
|
|
3876
3949
|
code: "VIBE_MIGRATION",
|
|
3877
3950
|
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.',
|
|
3878
3951
|
meta: { reason: "unsupported-self-managed-script", problem, migration, statementIndex: index, direction }
|
|
@@ -3928,7 +4001,7 @@ async function executeStatement(params) {
|
|
|
3928
4001
|
const rows = await executor({ text: statement, values: [] });
|
|
3929
4002
|
if (rows.length > 0) {
|
|
3930
4003
|
const sample = rows.slice(0, 5).map((row) => `${String(row.table)} rowid ${String(row.rowid)} \u2192 ${String(row.parent)}`).join("; ");
|
|
3931
|
-
throw new
|
|
4004
|
+
throw new VibeError15({
|
|
3932
4005
|
code: "VIBE_MIGRATION",
|
|
3933
4006
|
message: `foreign key check failed after table rebuild: ${rows.length} violation(s) \u2014 ${sample}`,
|
|
3934
4007
|
meta: { violations: rows }
|
|
@@ -3983,7 +4056,7 @@ async function prepareOperatorClassDiff(params) {
|
|
|
3983
4056
|
}
|
|
3984
4057
|
|
|
3985
4058
|
// packages/migrate/src/postgres-default-equivalence.ts
|
|
3986
|
-
import { VibeError as
|
|
4059
|
+
import { VibeError as VibeError16 } from "@vibeorm/schema";
|
|
3987
4060
|
import { postgresDialect as postgresDialect8, renderPostgresDefaultFunctionIdentity } from "@vibeorm/sql";
|
|
3988
4061
|
var IDENTIFIER = '(?:"(?:[^"\\u0000]|"")+"|[A-Za-z_][A-Za-z_0-9$]*)';
|
|
3989
4062
|
var CALL = new RegExp(`^\\s*(${IDENTIFIER})(?:\\s*\\.\\s*(${IDENTIFIER}))?\\s*\\(\\s*\\)\\s*$`);
|
|
@@ -4002,7 +4075,7 @@ async function equivalentPostgresDefaultFunction(params) {
|
|
|
4002
4075
|
return false;
|
|
4003
4076
|
const query = renderPostgresDefaultFunctionIdentity({ dialect: postgresDialect8, table: params.table, column: params.column });
|
|
4004
4077
|
const rows = await params.executor({ text: query.text, values: [...query.values] }).catch((cause) => {
|
|
4005
|
-
throw new
|
|
4078
|
+
throw new VibeError16({ code: "VIBE_MIGRATION", message: "Cannot inspect the stored default function identity", meta: { reason: "default-inspection-unavailable", table: params.table, column: params.column }, cause });
|
|
4006
4079
|
});
|
|
4007
4080
|
if (rows.length !== 1)
|
|
4008
4081
|
return false;
|
|
@@ -4097,7 +4170,7 @@ async function inspectSqlObject(params) {
|
|
|
4097
4170
|
}
|
|
4098
4171
|
}
|
|
4099
4172
|
return { state: "present", definition: { identity: params.identity, facts, dependencies: [], format: "catalog-v2" } };
|
|
4100
|
-
}).catch((error) => ({ state: "unavailable", reason: error instanceof
|
|
4173
|
+
}).catch((error) => ({ state: "unavailable", reason: error instanceof VibeError17 ? String(error.meta.feature ?? error.code) : "catalog-query-failed" }));
|
|
4101
4174
|
}
|
|
4102
4175
|
function postgresInspection(params) {
|
|
4103
4176
|
if (!params.addresses.every((address) => typeof address === "string"))
|
|
@@ -4112,7 +4185,7 @@ function postgresInspection(params) {
|
|
|
4112
4185
|
return { state: "present", definition: { identity: params.identity, facts, dependencies, format: "catalog-v2" } };
|
|
4113
4186
|
}
|
|
4114
4187
|
function unavailableReason(params) {
|
|
4115
|
-
return params.error instanceof
|
|
4188
|
+
return params.error instanceof VibeError17 ? String(params.error.meta.feature ?? params.error.meta.reason ?? params.error.code) : "catalog-query-failed";
|
|
4116
4189
|
}
|
|
4117
4190
|
async function readBatch(params) {
|
|
4118
4191
|
const positions = new Set(params.batch.positions);
|
|
@@ -4120,7 +4193,7 @@ async function readBatch(params) {
|
|
|
4120
4193
|
const rows = new Map;
|
|
4121
4194
|
for (const row of await params.executor({ text: params.batch.text, values: [...params.batch.values] })) {
|
|
4122
4195
|
if (typeof row.position !== "number" || !Number.isInteger(row.position) || !positions.has(row.position))
|
|
4123
|
-
throw new
|
|
4196
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "Invalid batched catalog identity", meta: { reason: "catalog-query-failed" } });
|
|
4124
4197
|
const matches = rows.get(row.position) ?? [];
|
|
4125
4198
|
matches.push(row);
|
|
4126
4199
|
rows.set(row.position, matches);
|
|
@@ -4165,7 +4238,7 @@ async function assertCatalogVisibility(params) {
|
|
|
4165
4238
|
const grants = new Set(rows.map((row) => row.privilege));
|
|
4166
4239
|
const missing = ["SELECT", "TRIGGER", "SHOW VIEW", "EXECUTE"].filter((privilege) => !grants.has(privilege));
|
|
4167
4240
|
if (missing.length > 0)
|
|
4168
|
-
throw new
|
|
4241
|
+
throw new VibeError17({ 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 } });
|
|
4169
4242
|
}
|
|
4170
4243
|
var CATALOG_V1_OMITTED_FACTS = Object.freeze({
|
|
4171
4244
|
function: ["definerOwner"],
|
|
@@ -4189,9 +4262,20 @@ function compare(params) {
|
|
|
4189
4262
|
differences.push({ attribute: "dependencies", expected, actual });
|
|
4190
4263
|
return differences;
|
|
4191
4264
|
}
|
|
4265
|
+
function compareInspectedSqlObject(params) {
|
|
4266
|
+
const { identity, ownership, expected, live } = params;
|
|
4267
|
+
if (live.state !== "present")
|
|
4268
|
+
return { identity, ownership, state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} };
|
|
4269
|
+
if (expected === undefined)
|
|
4270
|
+
return identity.kind === "namespace" ? { identity, ownership, state: "matching", differences: [] } : { identity, ownership, state: "unavailable", reason: "independent-definition-required", differences: [] };
|
|
4271
|
+
if (params.dialect === "postgres" && identity.kind === "table" && (expected.facts.persistence === undefined || expected.facts.options === undefined))
|
|
4272
|
+
return { identity, ownership, state: "unavailable", reason: "table-physical-reference-required", differences: compare({ expected, live: live.definition }) };
|
|
4273
|
+
const differences = compare({ expected, live: live.definition });
|
|
4274
|
+
return { identity, ownership, state: differences.length === 0 ? "matching" : "changed", differences };
|
|
4275
|
+
}
|
|
4192
4276
|
function assertProbeMode(params) {
|
|
4193
4277
|
if (params.probe !== undefined && params.probe !== "none" && DIALECT_CAPABILITIES2[params.dialect].checkNormalization !== params.probe)
|
|
4194
|
-
throw new
|
|
4278
|
+
throw new VibeError17({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `${params.probe} CHECK probes are unavailable on ${params.dialect}`, meta: { feature: "checkProbe", dialect: params.dialect } });
|
|
4195
4279
|
}
|
|
4196
4280
|
async function verifyMigrationState(params) {
|
|
4197
4281
|
return verifyState(params);
|
|
@@ -4214,18 +4298,7 @@ async function verifyState(params) {
|
|
|
4214
4298
|
const expected = definitions.get(key);
|
|
4215
4299
|
const live = inspections === undefined ? await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity }) : inspections[position];
|
|
4216
4300
|
position++;
|
|
4217
|
-
|
|
4218
|
-
objects.push({ identity, ownership, state: live.state, differences: [], ...live.state === "unavailable" ? { reason: live.reason } : {} });
|
|
4219
|
-
} else if (expected === undefined && identity.kind === "namespace") {
|
|
4220
|
-
objects.push({ identity, ownership, state: "matching", differences: [] });
|
|
4221
|
-
} else if (expected === undefined) {
|
|
4222
|
-
objects.push({ identity, ownership, state: "unavailable", reason: "independent-definition-required", differences: [] });
|
|
4223
|
-
} else if (params.dialect === "postgres" && identity.kind === "table" && (expected.facts.persistence === undefined || expected.facts.options === undefined)) {
|
|
4224
|
-
objects.push({ identity, ownership, state: "unavailable", reason: "table-physical-reference-required", differences: compare({ expected, live: live.definition }) });
|
|
4225
|
-
} else {
|
|
4226
|
-
const differences = compare({ expected, live: live.definition });
|
|
4227
|
-
objects.push({ identity, ownership, state: differences.length === 0 ? "matching" : "changed", differences });
|
|
4228
|
-
}
|
|
4301
|
+
objects.push(compareInspectedSqlObject({ identity, ownership, expected, live, dialect: params.dialect }));
|
|
4229
4302
|
}
|
|
4230
4303
|
if (params.dialect === "postgres" && schemaNamespaces({ schema: params.expected.schema }).length > 0 || params.expected.schema.models.length > 0 || params.expected.schema.enums.length > 0)
|
|
4231
4304
|
objects.push(...await verifySchemaObjects(params));
|
|
@@ -4394,7 +4467,7 @@ async function verifyMigrationTransition(params) {
|
|
|
4394
4467
|
}
|
|
4395
4468
|
async function verifySqliteTransitionBeforeRestore(params) {
|
|
4396
4469
|
if (params.dialect !== "sqlite")
|
|
4397
|
-
throw new
|
|
4470
|
+
throw new VibeError17({ code: "VIBE_INTERNAL", message: "SQLite rebuild verification requires the SQLite dialect" });
|
|
4398
4471
|
await executeStatement({ executor: params.executor, dialect: "sqlite", statement: "PRAGMA foreign_key_check;" });
|
|
4399
4472
|
return verifyTransition({ ...params, sqliteRestorePending: true });
|
|
4400
4473
|
}
|
|
@@ -4404,7 +4477,7 @@ async function verifyTransition(params) {
|
|
|
4404
4477
|
for (const identity of params.removals ?? []) {
|
|
4405
4478
|
const owner = externalObjectOwner({ identity, external });
|
|
4406
4479
|
if (owner !== undefined)
|
|
4407
|
-
throw new
|
|
4480
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "Transition removals cannot target externally owned objects", meta: { reason: "external-owned-removal", owner, identity } });
|
|
4408
4481
|
}
|
|
4409
4482
|
params = { ...params, from: projectExternalState({ state: params.from, external }), to: projectExternalState({ state: params.to, external }) };
|
|
4410
4483
|
}
|
|
@@ -4429,12 +4502,12 @@ async function migrationNamespace(params) {
|
|
|
4429
4502
|
const rows = await params.executor({ text: query.text, values: [] });
|
|
4430
4503
|
const namespace = rows[0]?.namespace;
|
|
4431
4504
|
if (typeof namespace !== "string" || namespace.length === 0)
|
|
4432
|
-
throw new
|
|
4505
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "Cannot determine the migration namespace", meta: { reason: "inspection-unavailable" } });
|
|
4433
4506
|
return namespace;
|
|
4434
4507
|
}
|
|
4435
4508
|
function assertMigrationState(params) {
|
|
4436
4509
|
if (!params.report.inSync)
|
|
4437
|
-
throw new
|
|
4510
|
+
throw new VibeError17({ 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") } });
|
|
4438
4511
|
}
|
|
4439
4512
|
async function deparsePostgresCheck(params) {
|
|
4440
4513
|
const definition = await deparsePostgresCheckDefinition({ ...params, constraint: "vibeorm_check_probe" });
|
|
@@ -4448,7 +4521,7 @@ async function deparsePostgresCheckDefinition(params) {
|
|
|
4448
4521
|
await params.executor({ text: query.text });
|
|
4449
4522
|
const result = await inspectSqlObject({ executor: params.executor, dialect: "postgres", identity: { kind: "constraint", constraintType: "check", table: params.table, name } });
|
|
4450
4523
|
if (result.state !== "present" || typeof result.definition.facts.definition !== "string")
|
|
4451
|
-
throw new
|
|
4524
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "CHECK normalization probe unavailable", meta: { reason: "check-inspection-unavailable" } });
|
|
4452
4525
|
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 } };
|
|
4453
4526
|
} finally {
|
|
4454
4527
|
await params.executor({ text: "ROLLBACK TO SAVEPOINT vibeorm_check_probe" });
|
|
@@ -4466,7 +4539,7 @@ async function deparseMysqlCheck(params) {
|
|
|
4466
4539
|
const source = await params.executor({ text: read.text, values: [] });
|
|
4467
4540
|
const definition = source[0]?.["Create Table"];
|
|
4468
4541
|
if (typeof definition !== "string")
|
|
4469
|
-
throw new
|
|
4542
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "Cannot read temporary clone definition" });
|
|
4470
4543
|
const drop = renderMysqlCheckProbe({ dialect, probe, operation: "drop" });
|
|
4471
4544
|
await params.executor({ text: drop.text });
|
|
4472
4545
|
created = false;
|
|
@@ -4477,7 +4550,7 @@ async function deparseMysqlCheck(params) {
|
|
|
4477
4550
|
const normalizedDefinition = rows[0]?.["Create Table"];
|
|
4478
4551
|
const check = typeof normalizedDefinition === "string" ? parseSqliteNamedChecks({ tableSql: normalizedDefinition }).find((entry) => entry.name === probe) : undefined;
|
|
4479
4552
|
if (check === undefined)
|
|
4480
|
-
throw new
|
|
4553
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "MySQL temporary CHECK deparse unavailable", meta: { reason: "check-inspection-unavailable" } });
|
|
4481
4554
|
return check.expression;
|
|
4482
4555
|
} finally {
|
|
4483
4556
|
if (created) {
|
|
@@ -4504,7 +4577,7 @@ async function prepareCheckDiff(params) {
|
|
|
4504
4577
|
const desired = await deparsePostgresCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "rollback" });
|
|
4505
4578
|
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
4506
4579
|
if (live.state !== "present")
|
|
4507
|
-
throw new
|
|
4580
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
|
|
4508
4581
|
if (live.definition.facts.definition === desired) {
|
|
4509
4582
|
checks.push(next);
|
|
4510
4583
|
continue;
|
|
@@ -4516,7 +4589,7 @@ async function prepareCheckDiff(params) {
|
|
|
4516
4589
|
const desired = await deparseMysqlCheck({ executor: params.executor, table: identity.table, expression: next.expression, probe: "temporary" });
|
|
4517
4590
|
const live = await inspectSqlObject({ executor: params.executor, dialect: params.dialect, identity });
|
|
4518
4591
|
if (live.state !== "present")
|
|
4519
|
-
throw new
|
|
4592
|
+
throw new VibeError17({ code: "VIBE_MIGRATION", message: "Live CHECK inspection unavailable", meta: { reason: "check-inspection-unavailable", identity } });
|
|
4520
4593
|
checks.push(desired === live.definition.facts.definition ? next : { name: check.name, expression: check.expression });
|
|
4521
4594
|
continue;
|
|
4522
4595
|
}
|
|
@@ -4529,7 +4602,7 @@ async function prepareCheckDiff(params) {
|
|
|
4529
4602
|
checks.push({ name: check.name, expression: check.expression });
|
|
4530
4603
|
continue;
|
|
4531
4604
|
}
|
|
4532
|
-
throw new
|
|
4605
|
+
throw new VibeError17({ 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 } });
|
|
4533
4606
|
}
|
|
4534
4607
|
models.push({ ...model, checks });
|
|
4535
4608
|
}
|
|
@@ -4556,7 +4629,7 @@ async function inspectMigrationInventory(params) {
|
|
|
4556
4629
|
for (const row of rows) {
|
|
4557
4630
|
const name = row.name;
|
|
4558
4631
|
if (typeof name !== "string" || typeof row.kind !== "string")
|
|
4559
|
-
throw new
|
|
4632
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "Malformed catalog identity", meta: { reason: "inventory-unavailable" } });
|
|
4560
4633
|
const qualified = { namespace: params.namespace, name };
|
|
4561
4634
|
const table = { namespace: params.namespace, name: String(row.parent ?? "") };
|
|
4562
4635
|
let identity;
|
|
@@ -4582,7 +4655,7 @@ async function inspectMigrationInventory(params) {
|
|
|
4582
4655
|
case "constraint": {
|
|
4583
4656
|
const constraintType = kinds[String(row.subtype)];
|
|
4584
4657
|
if (constraintType === undefined)
|
|
4585
|
-
throw new
|
|
4658
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "Unknown constraint catalog kind", meta: { reason: "inventory-unavailable" } });
|
|
4586
4659
|
identity = { kind: "constraint", table, name, constraintType };
|
|
4587
4660
|
break;
|
|
4588
4661
|
}
|
|
@@ -4592,17 +4665,17 @@ async function inspectMigrationInventory(params) {
|
|
|
4592
4665
|
const query = renderMysqlObjectMembers2({ dialect: getSqlDialect3({ dialect: params.dialect }), identity: { kind: "function", name: qualified, argumentTypes: [] } });
|
|
4593
4666
|
const members = await params.executor({ text: query.text, values: [...query.values] });
|
|
4594
4667
|
if (members.length === 0)
|
|
4595
|
-
throw new
|
|
4668
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "MySQL function signature unavailable", meta: { reason: "inventory-unavailable", name } });
|
|
4596
4669
|
argumentTypes = members.filter((member) => member.mode !== null).map((member) => ({ namespace: "mysql", name: String(member.type) }));
|
|
4597
4670
|
}
|
|
4598
4671
|
const candidate = { kind: "function", name: qualified, argumentTypes };
|
|
4599
4672
|
if (!isSqlObjectIdentity2(candidate))
|
|
4600
|
-
throw new
|
|
4673
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: "Invalid function signature in catalog", meta: { reason: "inventory-unavailable" } });
|
|
4601
4674
|
identity = candidate;
|
|
4602
4675
|
break;
|
|
4603
4676
|
}
|
|
4604
4677
|
default:
|
|
4605
|
-
throw new
|
|
4678
|
+
throw new VibeError18({ code: "VIBE_MIGRATION", message: `Unknown catalog object kind ${row.kind}`, meta: { reason: "inventory-unavailable" } });
|
|
4606
4679
|
}
|
|
4607
4680
|
objects.push({ identity, ...typeof row.backing === "string" ? { backingConstraint: row.backing } : {} });
|
|
4608
4681
|
if (params.dialect === "sqlite" && row.kind === "table" && typeof row.definition === "string" && !/^\s*CREATE\s+VIRTUAL\s+TABLE\b/i.test(row.definition)) {
|
|
@@ -4646,7 +4719,7 @@ async function inspectMigrationInventory(params) {
|
|
|
4646
4719
|
|
|
4647
4720
|
// packages/migrate/src/module-ownership.ts
|
|
4648
4721
|
function refusal(params) {
|
|
4649
|
-
return new
|
|
4722
|
+
return new VibeError19({ code: "VIBE_MIGRATION", message: "Module ownership or dependencies cannot be reconciled with the declared and live schema; no package SQL was authorized", meta: params });
|
|
4650
4723
|
}
|
|
4651
4724
|
function identities(params) {
|
|
4652
4725
|
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)];
|
|
@@ -4707,7 +4780,7 @@ function assertModuleOwnership(params) {
|
|
|
4707
4780
|
throw refusal({ reason: "module-mutates-application-state", moduleId: origin.moduleId });
|
|
4708
4781
|
}
|
|
4709
4782
|
function dependencyUnavailable(params) {
|
|
4710
|
-
return new
|
|
4783
|
+
return new VibeError19({ 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 } });
|
|
4711
4784
|
}
|
|
4712
4785
|
async function assertCrossNamespaceModuleDependencies(params) {
|
|
4713
4786
|
const { executor, origin } = params;
|
|
@@ -4769,15 +4842,15 @@ async function assertApplicationModuleState(params) {
|
|
|
4769
4842
|
const options = { executor: params.executor, dialect: origin.dialect, from: state.from, to: state.to, removals: state.unmanagedRemovals, ...params.probe === undefined ? {} : { probe: params.probe } };
|
|
4770
4843
|
const report = params.sqliteRestorePending === true ? await verifySqliteTransitionBeforeRestore(options) : await verifyMigrationTransition(options);
|
|
4771
4844
|
if (!report.inSync)
|
|
4772
|
-
throw new
|
|
4845
|
+
throw new VibeError19({ 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 } });
|
|
4773
4846
|
}
|
|
4774
4847
|
}
|
|
4775
4848
|
|
|
4776
4849
|
// packages/migrate/src/module-adoption.ts
|
|
4777
|
-
import { VibeError as
|
|
4850
|
+
import { VibeError as VibeError20 } from "@vibeorm/schema";
|
|
4778
4851
|
var MODULE_ADOPTION_METADATA = Object.freeze({ filename: "module-adoption.json", version: 1 });
|
|
4779
4852
|
function invalid3() {
|
|
4780
|
-
return new
|
|
4853
|
+
return new VibeError20({ code: "VIBE_MIGRATION", message: "Local module adoption proof is malformed or lacks immutable source provenance; restore the reviewed local history", meta: { reason: "module-adoption-invalid" } });
|
|
4781
4854
|
}
|
|
4782
4855
|
function assertMarker(params) {
|
|
4783
4856
|
const value = params.value;
|
|
@@ -4813,7 +4886,7 @@ function serializeModuleAdoption(params) {
|
|
|
4813
4886
|
}
|
|
4814
4887
|
|
|
4815
4888
|
// packages/migrate/src/sql-statements.ts
|
|
4816
|
-
import { VibeError as
|
|
4889
|
+
import { VibeError as VibeError21 } from "@vibeorm/schema";
|
|
4817
4890
|
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;
|
|
4818
4891
|
var BLOCK_OPENERS = new Set(["BEGIN", "CASE", "IF", "LOOP", "WHILE", "REPEAT"]);
|
|
4819
4892
|
function splitSqlStatements(params) {
|
|
@@ -4889,7 +4962,7 @@ function splitSqlStatements(params) {
|
|
|
4889
4962
|
continue;
|
|
4890
4963
|
}
|
|
4891
4964
|
if (dialect === "mysql" || dialect === undefined) {
|
|
4892
|
-
throw new
|
|
4965
|
+
throw new VibeError21({ 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 } });
|
|
4893
4966
|
}
|
|
4894
4967
|
}
|
|
4895
4968
|
if (value === endQuote) {
|
|
@@ -4963,10 +5036,10 @@ function joinSqlStatements(params) {
|
|
|
4963
5036
|
|
|
4964
5037
|
// packages/migrate/src/module-origin.ts
|
|
4965
5038
|
import { createHash } from "crypto";
|
|
4966
|
-
import { VibeError as
|
|
5039
|
+
import { VibeError as VibeError22, isSqlObjectIdentity as isSqlObjectIdentity3, sqlObjectKey as sqlObjectKey8 } from "@vibeorm/schema";
|
|
4967
5040
|
var MODULE_ORIGIN_METADATA = Object.freeze({ filename: "module-origin.json", version: 1 });
|
|
4968
5041
|
function invalid4() {
|
|
4969
|
-
return new
|
|
5042
|
+
return new VibeError22({ 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" } });
|
|
4970
5043
|
}
|
|
4971
5044
|
function record3(value) {
|
|
4972
5045
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -5030,7 +5103,7 @@ function assertMaterializedModuleMigration(params) {
|
|
|
5030
5103
|
return;
|
|
5031
5104
|
const restored = restoreModuleMigration({ migration, origin: migration.moduleOrigin });
|
|
5032
5105
|
if (migration.desiredState === undefined || canonicalMigrationJson({ value: restored }) !== canonicalMigrationJson({ value: migration }))
|
|
5033
|
-
throw new
|
|
5106
|
+
throw new VibeError22({ code: "VIBE_MIGRATION", message: "Materialized module metadata differs from its immutable source", meta: { reason: "module-source-state-mismatch", moduleId: migration.moduleOrigin.moduleId } });
|
|
5034
5107
|
}
|
|
5035
5108
|
function restoreModuleMigration(params) {
|
|
5036
5109
|
const { migration, origin } = params;
|
|
@@ -5055,13 +5128,13 @@ function restoreModuleMigration(params) {
|
|
|
5055
5128
|
// packages/migrate/src/fs.ts
|
|
5056
5129
|
import { readFile, readdir } from "fs/promises";
|
|
5057
5130
|
import { join } from "path";
|
|
5058
|
-
import { VibeError as
|
|
5131
|
+
import { VibeError as VibeError37, assertValidSchemaIR as assertValidSchemaIR4 } from "@vibeorm/schema";
|
|
5059
5132
|
|
|
5060
5133
|
// packages/migrate/src/migration-checksum.ts
|
|
5061
5134
|
import { createHash as createHash2 } from "crypto";
|
|
5062
5135
|
|
|
5063
5136
|
// packages/migrate/src/execution.ts
|
|
5064
|
-
import { VibeError as
|
|
5137
|
+
import { VibeError as VibeError23 } from "@vibeorm/schema";
|
|
5065
5138
|
var EXECUTION_SIDECAR_FILENAME = "execution.json";
|
|
5066
5139
|
var EXECUTION_SIDECAR_VERSION = 1;
|
|
5067
5140
|
function isDefaultExecution(params) {
|
|
@@ -5100,7 +5173,7 @@ ${lines.join(`
|
|
|
5100
5173
|
`)}`;
|
|
5101
5174
|
}
|
|
5102
5175
|
function invalid5(params) {
|
|
5103
|
-
return new
|
|
5176
|
+
return new VibeError23({
|
|
5104
5177
|
code: "VIBE_MIGRATION",
|
|
5105
5178
|
message: `invalid execution metadata for migration "${params.migration}": ${params.detail}`,
|
|
5106
5179
|
meta: { reason: "execution-metadata-invalid", migration: params.migration }
|
|
@@ -5151,7 +5224,7 @@ function parseExecutionSidecar(params) {
|
|
|
5151
5224
|
try {
|
|
5152
5225
|
parsed = JSON.parse(params.text);
|
|
5153
5226
|
} catch (error) {
|
|
5154
|
-
throw new
|
|
5227
|
+
throw new VibeError23({
|
|
5155
5228
|
code: "VIBE_MIGRATION",
|
|
5156
5229
|
message: `invalid execution metadata for migration "${migration}": not valid JSON`,
|
|
5157
5230
|
meta: { reason: "execution-metadata-invalid", migration },
|
|
@@ -5250,7 +5323,7 @@ function migrationChecksum(params) {
|
|
|
5250
5323
|
}
|
|
5251
5324
|
|
|
5252
5325
|
// packages/migrate/src/shadow-guard.ts
|
|
5253
|
-
import { VibeError as
|
|
5326
|
+
import { VibeError as VibeError24 } from "@vibeorm/schema";
|
|
5254
5327
|
import { getSqlDialect as getSqlDialect5, renderShadowOperation } from "@vibeorm/sql";
|
|
5255
5328
|
var replayExecutors = new WeakSet;
|
|
5256
5329
|
function isShadowReplayExecutor(params) {
|
|
@@ -5262,7 +5335,7 @@ async function assertDatabaseNotShadow(params) {
|
|
|
5262
5335
|
const [query] = renderShadowOperation({ dialect: getSqlDialect5({ dialect: params.dialect }), operation: { kind: "markerExists" } });
|
|
5263
5336
|
const rows = await params.executor({ text: query.text, values: [...query.values] });
|
|
5264
5337
|
if (rows.length !== 0)
|
|
5265
|
-
throw new
|
|
5338
|
+
throw new VibeError24({ 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" } });
|
|
5266
5339
|
}
|
|
5267
5340
|
function runShadowReplay(params) {
|
|
5268
5341
|
replayExecutors.add(params.executor);
|
|
@@ -5272,7 +5345,7 @@ function runShadowReplay(params) {
|
|
|
5272
5345
|
}
|
|
5273
5346
|
|
|
5274
5347
|
// packages/migrate/src/external-dependencies.ts
|
|
5275
|
-
import { VibeError as
|
|
5348
|
+
import { VibeError as VibeError25, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
5276
5349
|
import { getSqlDialect as getSqlDialect6, renderExternalDependents } from "@vibeorm/sql";
|
|
5277
5350
|
function externalDependencyTargets(params) {
|
|
5278
5351
|
return params.steps.flatMap((step) => {
|
|
@@ -5295,7 +5368,7 @@ function externalDependencyTargets(params) {
|
|
|
5295
5368
|
});
|
|
5296
5369
|
}
|
|
5297
5370
|
function unavailable() {
|
|
5298
|
-
return new
|
|
5371
|
+
return new VibeError25({ code: "VIBE_MIGRATION", message: "Malformed catalog dependency row", meta: { reason: "external-inventory-unavailable" } });
|
|
5299
5372
|
}
|
|
5300
5373
|
function text(params) {
|
|
5301
5374
|
const value = params.row[params.key];
|
|
@@ -5336,7 +5409,7 @@ function assertNoExternalDependents(params) {
|
|
|
5336
5409
|
const dependent = dependentOwner({ row, external: params.external });
|
|
5337
5410
|
if (dependent.owner === undefined)
|
|
5338
5411
|
continue;
|
|
5339
|
-
throw new
|
|
5412
|
+
throw new VibeError25({
|
|
5340
5413
|
code: "VIBE_MIGRATION",
|
|
5341
5414
|
message: "An externally owned object depends on an application object this change drops or retypes; automatic DDL is refused",
|
|
5342
5415
|
meta: { reason: "external-owned-dependency", owner: dependent.owner, ...dependent.identity === undefined ? {} : { identity: dependent.identity }, dependent: { type, identity }, dependency: target.dependency, referenced }
|
|
@@ -5357,9 +5430,9 @@ async function refuseExternalDependents(params) {
|
|
|
5357
5430
|
}
|
|
5358
5431
|
|
|
5359
5432
|
// packages/migrate/src/module-history.ts
|
|
5360
|
-
import { VibeError as
|
|
5433
|
+
import { VibeError as VibeError26, sqlObjectKey as sqlObjectKey9, providerToDialect as providerToDialect3 } from "@vibeorm/schema";
|
|
5361
5434
|
function refusal2(params) {
|
|
5362
|
-
return new
|
|
5435
|
+
return new VibeError26({ code: "VIBE_MIGRATION", message: `Module ${params.moduleId} history, ownership or predecessor state is inconsistent; restore or complete the reviewed upgrade first`, meta: params });
|
|
5363
5436
|
}
|
|
5364
5437
|
function moduleHistoryHeads(params) {
|
|
5365
5438
|
const heads = new Map;
|
|
@@ -5466,11 +5539,11 @@ function assertModuleUpgradeHistory(params) {
|
|
|
5466
5539
|
}
|
|
5467
5540
|
|
|
5468
5541
|
// packages/migrate/src/module-journal.ts
|
|
5469
|
-
import { VibeError as
|
|
5542
|
+
import { VibeError as VibeError28 } from "@vibeorm/schema";
|
|
5470
5543
|
import { getSqlDialect as getSqlDialect7, MODULE_JOURNAL_COLUMN, renderModuleJournal } from "@vibeorm/sql";
|
|
5471
5544
|
|
|
5472
5545
|
// packages/migrate/src/journal-read.ts
|
|
5473
|
-
import { VibeError as
|
|
5546
|
+
import { VibeError as VibeError27 } from "@vibeorm/schema";
|
|
5474
5547
|
var MISSING_TABLE_IDENTIFIERS = new Set(["42P01", "42S02", "ER_NO_SUCH_TABLE"]);
|
|
5475
5548
|
function isMissingTable(params) {
|
|
5476
5549
|
let current = params.cause;
|
|
@@ -5494,7 +5567,7 @@ async function readJournalRows(params) {
|
|
|
5494
5567
|
if (presence.state === "missing")
|
|
5495
5568
|
return;
|
|
5496
5569
|
}
|
|
5497
|
-
throw new
|
|
5570
|
+
throw new VibeError27({ code: "VIBE_MIGRATION", message: "Cannot inspect migration history; check SELECT privileges", meta: { reason: "history-unavailable" }, cause });
|
|
5498
5571
|
});
|
|
5499
5572
|
if (rows === undefined)
|
|
5500
5573
|
return;
|
|
@@ -5502,7 +5575,7 @@ async function readJournalRows(params) {
|
|
|
5502
5575
|
for (const row of rows) {
|
|
5503
5576
|
const index = row.statement_index;
|
|
5504
5577
|
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)) {
|
|
5505
|
-
throw new
|
|
5578
|
+
throw new VibeError27({ code: "VIBE_MIGRATION", message: "Malformed migration journal row", meta: { reason: "history-unavailable" } });
|
|
5506
5579
|
}
|
|
5507
5580
|
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 } : {} });
|
|
5508
5581
|
}
|
|
@@ -5511,7 +5584,7 @@ async function readJournalRows(params) {
|
|
|
5511
5584
|
|
|
5512
5585
|
// packages/migrate/src/module-journal.ts
|
|
5513
5586
|
function invalid6() {
|
|
5514
|
-
return new
|
|
5587
|
+
return new VibeError28({ 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" } });
|
|
5515
5588
|
}
|
|
5516
5589
|
function parseModuleJournalEntry(params) {
|
|
5517
5590
|
return parseMigrationMetadata({ text: params.text, failure: invalid6, parse: (value) => {
|
|
@@ -5570,7 +5643,7 @@ function moduleRecordComplete(params) {
|
|
|
5570
5643
|
}
|
|
5571
5644
|
|
|
5572
5645
|
// packages/migrate/src/runner.ts
|
|
5573
|
-
import { VibeError as
|
|
5646
|
+
import { VibeError as VibeError36 } from "@vibeorm/schema";
|
|
5574
5647
|
import {
|
|
5575
5648
|
DIALECT_CAPABILITIES as DIALECT_CAPABILITIES3,
|
|
5576
5649
|
getSqlDialect as getSqlDialect9,
|
|
@@ -5580,7 +5653,7 @@ import {
|
|
|
5580
5653
|
} from "@vibeorm/sql";
|
|
5581
5654
|
|
|
5582
5655
|
// packages/migrate/src/recovery.ts
|
|
5583
|
-
import { VibeError as
|
|
5656
|
+
import { VibeError as VibeError29 } from "@vibeorm/schema";
|
|
5584
5657
|
import {
|
|
5585
5658
|
getSqlDialect as getSqlDialect8,
|
|
5586
5659
|
renderColumnStateQuery,
|
|
@@ -5596,7 +5669,7 @@ function constraintUnverifiable(params) {
|
|
|
5596
5669
|
});
|
|
5597
5670
|
}
|
|
5598
5671
|
function conflict2(params) {
|
|
5599
|
-
return new
|
|
5672
|
+
return new VibeError29({
|
|
5600
5673
|
code: "VIBE_MIGRATION",
|
|
5601
5674
|
message: params.message,
|
|
5602
5675
|
meta: { reason: params.reason, ...params.meta }
|
|
@@ -5701,10 +5774,10 @@ async function establishOperationState(params) {
|
|
|
5701
5774
|
|
|
5702
5775
|
// packages/migrate/src/differ.ts
|
|
5703
5776
|
import { assertIndexOperatorClasses as assertIndexOperatorClasses2, isEnumType as isEnumType6, qualifiedSqlName as qualifiedSqlName9, unsupportedCapability as unsupportedCapability10 } from "@vibeorm/schema";
|
|
5704
|
-
import { VibeError as
|
|
5777
|
+
import { VibeError as VibeError31 } from "@vibeorm/schema";
|
|
5705
5778
|
|
|
5706
5779
|
// packages/migrate/src/renames.ts
|
|
5707
|
-
import { VibeError as
|
|
5780
|
+
import { VibeError as VibeError30, unsupportedCapability as unsupportedCapability9 } from "@vibeorm/schema";
|
|
5708
5781
|
function tableOf(model) {
|
|
5709
5782
|
return model.dbName ?? model.name;
|
|
5710
5783
|
}
|
|
@@ -5715,7 +5788,7 @@ function hasColumn(params) {
|
|
|
5715
5788
|
return params.model.fields.some((field) => (field.dbName ?? field.name) === params.column);
|
|
5716
5789
|
}
|
|
5717
5790
|
function ambiguous(params) {
|
|
5718
|
-
return new
|
|
5791
|
+
return new VibeError30({
|
|
5719
5792
|
code: "VIBE_MIGRATION",
|
|
5720
5793
|
message: `rename refused: ${params.detail}`,
|
|
5721
5794
|
meta: { reason: "rename-ambiguous", ...params.meta }
|
|
@@ -5817,17 +5890,34 @@ function planRenames(params) {
|
|
|
5817
5890
|
|
|
5818
5891
|
// packages/migrate/src/differ.ts
|
|
5819
5892
|
function diffSchemas(params) {
|
|
5893
|
+
return diffSchemasWithNormalization(params).steps;
|
|
5894
|
+
}
|
|
5895
|
+
function diffSchemasWithNormalization(params) {
|
|
5820
5896
|
const dialect = params.dialect ?? "postgres";
|
|
5821
5897
|
assertIndexOperatorClasses2({ schema: params.to, dialect });
|
|
5822
|
-
for (const kind of ["models", "enums"])
|
|
5898
|
+
for (const kind of ["models", "enums"]) {
|
|
5899
|
+
const sourceByName = new Map;
|
|
5900
|
+
const sourcePhysical = new Set;
|
|
5901
|
+
const targetPhysical = new Set(params.to[kind].map((entry) => physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect })));
|
|
5902
|
+
const removedByBasename = new Map;
|
|
5903
|
+
for (const entry of params.from[kind]) {
|
|
5904
|
+
if (!sourceByName.has(entry.name))
|
|
5905
|
+
sourceByName.set(entry.name, entry);
|
|
5906
|
+
const basename = entry.dbName ?? entry.name;
|
|
5907
|
+
const key = physicalNameKey({ name: basename, namespace: entry.namespace, dialect });
|
|
5908
|
+
sourcePhysical.add(key);
|
|
5909
|
+
if (!targetPhysical.has(key) && !removedByBasename.has(basename))
|
|
5910
|
+
removedByBasename.set(basename, entry);
|
|
5911
|
+
}
|
|
5823
5912
|
for (const entry of params.to[kind]) {
|
|
5824
5913
|
const key = physicalNameKey({ name: entry.dbName ?? entry.name, namespace: entry.namespace, dialect });
|
|
5825
|
-
const before =
|
|
5826
|
-
if (before !== undefined && qualifiedSqlName9({ name: entry.name, namespace: before.namespace }).namespace !== qualifiedSqlName9({ name: entry.name, namespace: entry.namespace }).namespace && !
|
|
5914
|
+
const before = sourceByName.get(entry.name) ?? removedByBasename.get(entry.dbName ?? entry.name);
|
|
5915
|
+
if (before !== undefined && qualifiedSqlName9({ name: entry.name, namespace: before.namespace }).namespace !== qualifiedSqlName9({ name: entry.name, namespace: entry.namespace }).namespace && !sourcePhysical.has(key))
|
|
5827
5916
|
throw unsupportedCapability10({ dialect, feature: "automaticNamespaceMove", hint: "Moving populated objects to a fixed namespace requires an explicit reviewed migration" });
|
|
5828
5917
|
}
|
|
5829
|
-
|
|
5830
|
-
|
|
5918
|
+
}
|
|
5919
|
+
let from = normalizeSchema({ schema: params.from, dialect });
|
|
5920
|
+
const to = normalizeSchema({ schema: params.to, dialect });
|
|
5831
5921
|
for (const schema of [params.from, params.to])
|
|
5832
5922
|
if (dialect !== "postgres" && schema.models.some((model) => (model.exclusions?.length ?? 0) > 0))
|
|
5833
5923
|
throw unsupportedCapability10({ dialect, feature: "exclusionConstraints" });
|
|
@@ -5836,11 +5926,12 @@ function diffSchemas(params) {
|
|
|
5836
5926
|
if (dialect !== "postgres")
|
|
5837
5927
|
refuseScalarListColumns({ schema: params.to, dialect });
|
|
5838
5928
|
const renamed = planRenames({ from: params.from, to: params.to, renames: params.renames ?? [], dialect });
|
|
5929
|
+
if (renamed.from !== params.from)
|
|
5930
|
+
from = normalizeSchema({ schema: renamed.from, dialect });
|
|
5839
5931
|
params = { ...params, from: renamed.from };
|
|
5840
|
-
const
|
|
5841
|
-
const to = normalizeSchema({ schema: params.to, dialect });
|
|
5932
|
+
const sourceNamespaces = new Set(dialect === "postgres" ? schemaNamespaces({ schema: params.from }) : []);
|
|
5842
5933
|
const buckets = {
|
|
5843
|
-
createNamespace: dialect === "postgres" ? schemaNamespaces({ schema: params.to }).filter((namespace) => !
|
|
5934
|
+
createNamespace: dialect === "postgres" ? schemaNamespaces({ schema: params.to }).filter((namespace) => !sourceNamespaces.has(namespace)).map((namespace) => ({ kind: "createNamespace", namespace, destructive: false })) : [],
|
|
5844
5935
|
dropExclusion: [],
|
|
5845
5936
|
addExclusion: [],
|
|
5846
5937
|
dropView: [],
|
|
@@ -5868,16 +5959,22 @@ function diffSchemas(params) {
|
|
|
5868
5959
|
diffViews({ from, to, buckets });
|
|
5869
5960
|
diffJoinTables({ from, to, buckets });
|
|
5870
5961
|
diffTables({ from, to, buckets, dialect });
|
|
5962
|
+
const sourceModels = new Map;
|
|
5963
|
+
for (const model of params.from.models) {
|
|
5964
|
+
const key = physicalNameKey({ name: model.dbName ?? model.name, namespace: model.namespace, dialect });
|
|
5965
|
+
if (!sourceModels.has(key))
|
|
5966
|
+
sourceModels.set(key, model);
|
|
5967
|
+
}
|
|
5871
5968
|
for (const model of params.to.models) {
|
|
5872
5969
|
const table = model.dbName ?? model.name;
|
|
5873
5970
|
const placement = model.namespace === undefined ? {} : { namespace: model.namespace };
|
|
5874
|
-
const previous =
|
|
5971
|
+
const previous = sourceModels.get(physicalNameKey({ name: table, namespace: model.namespace, dialect }));
|
|
5875
5972
|
for (const exclusion of model.exclusions ?? []) {
|
|
5876
5973
|
const before = previous?.exclusions?.find((entry) => entry.name === exclusion.name);
|
|
5877
5974
|
if (before?.definition === exclusion.definition)
|
|
5878
5975
|
continue;
|
|
5879
5976
|
if (before?.catalogDefinition)
|
|
5880
|
-
throw new
|
|
5977
|
+
throw new VibeError31({ code: "VIBE_MIGRATION", message: "Exclusion comparison requires normalized evidence", meta: { reason: "exclusion-inspection-unavailable", table, name: exclusion.name } });
|
|
5881
5978
|
if (before !== undefined)
|
|
5882
5979
|
buckets.dropExclusion.push({ kind: "dropExclusion", table, ...placement, exclusion: before, destructive: false });
|
|
5883
5980
|
buckets.addExclusion.push({ kind: "addExclusion", table, ...placement, exclusion, destructive: false });
|
|
@@ -5887,7 +5984,8 @@ function diffSchemas(params) {
|
|
|
5887
5984
|
buckets.dropExclusion.push({ kind: "dropExclusion", table, exclusion, destructive: false });
|
|
5888
5985
|
}
|
|
5889
5986
|
refuseDependentEnumStorageChanges({ from, to, buckets, dialect });
|
|
5890
|
-
|
|
5987
|
+
refuseMysqlForeignKeyColumnRewrites({ from, to, buckets, dialect });
|
|
5988
|
+
return { from, to, steps: withRlsSteps({ from: params.from, to: params.to, steps: [
|
|
5891
5989
|
...buckets.createNamespace,
|
|
5892
5990
|
...renamed.steps,
|
|
5893
5991
|
...buckets.dropView,
|
|
@@ -5911,7 +6009,7 @@ function diffSchemas(params) {
|
|
|
5911
6009
|
...buckets.addForeignKey,
|
|
5912
6010
|
...buckets.createJoinTable,
|
|
5913
6011
|
...buckets.dropEnum
|
|
5914
|
-
] });
|
|
6012
|
+
] }) };
|
|
5915
6013
|
}
|
|
5916
6014
|
function refuseScalarListColumns(params) {
|
|
5917
6015
|
const { schema, dialect } = params;
|
|
@@ -5978,6 +6076,40 @@ function refuseDependentEnumStorageChanges(params) {
|
|
|
5978
6076
|
}
|
|
5979
6077
|
}
|
|
5980
6078
|
}
|
|
6079
|
+
function refuseMysqlForeignKeyColumnRewrites(params) {
|
|
6080
|
+
const { from, to, buckets, dialect } = params;
|
|
6081
|
+
if (dialect !== "mysql")
|
|
6082
|
+
return;
|
|
6083
|
+
const droppedFks = new Set(buckets.dropForeignKey.map((step) => step.kind === "dropForeignKey" ? `${step.table}\x00${step.constraintName}` : ""));
|
|
6084
|
+
for (const step of buckets.alterColumn) {
|
|
6085
|
+
if (step.kind !== "alterColumn")
|
|
6086
|
+
continue;
|
|
6087
|
+
const collation = step.changes.includes("collation");
|
|
6088
|
+
const type = step.changes.includes("type") && !isMysqlVarcharWidening({ from: step.from, to: step.to });
|
|
6089
|
+
if (!collation && !type)
|
|
6090
|
+
continue;
|
|
6091
|
+
const joined = [...from.joinTables].some(([key, join]) => to.joinTables.has(key) && [join.a, join.b].some((end) => end.table === step.table && end.column === step.column));
|
|
6092
|
+
const foreign = [...from.tables].some(([tableKey, table]) => [...table.fks].some(([fkKey, fk]) => to.tables.get(tableKey)?.fks.has(fkKey) === true && !droppedFks.has(`${table.table}\x00${fk.constraintName}`) && (table.table === step.table && fk.columns.includes(step.column) || fk.refTable === step.table && fk.refColumns.includes(step.column))));
|
|
6093
|
+
if (!joined && !foreign)
|
|
6094
|
+
continue;
|
|
6095
|
+
throw unsupportedCapability10({
|
|
6096
|
+
dialect,
|
|
6097
|
+
feature: `${collation ? "collation" : "type"} change of "${step.table}"."${step.column}" ${joined ? "referenced by an implicit many-to-many join table" : "in a foreign key"}`,
|
|
6098
|
+
hint: `drop the foreign key, change both columns' ${collation ? "collation" : "type"} and re-add it in a reviewed manual migration`
|
|
6099
|
+
});
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
function isMysqlVarcharWidening(params) {
|
|
6103
|
+
const length = (field) => {
|
|
6104
|
+
if (isEnumType6(field.type) || field.isList || field.isUnsupported === true)
|
|
6105
|
+
return null;
|
|
6106
|
+
const match = /^varchar\((\d+)\)$/.exec(normalizeMysqlType({ raw: mysqlColumnTypeSql({ field, enums: new Map }) }));
|
|
6107
|
+
return match === null ? null : Number(match[1]);
|
|
6108
|
+
};
|
|
6109
|
+
const before = length(params.from);
|
|
6110
|
+
const after = length(params.to);
|
|
6111
|
+
return before !== null && after !== null && after >= before;
|
|
6112
|
+
}
|
|
5981
6113
|
function diffViews(params) {
|
|
5982
6114
|
const { from, to, buckets } = params;
|
|
5983
6115
|
for (const [name, view] of from.views) {
|
|
@@ -6133,7 +6265,7 @@ function diffOneTable(params) {
|
|
|
6133
6265
|
from: before.field,
|
|
6134
6266
|
to: column.field,
|
|
6135
6267
|
changes,
|
|
6136
|
-
destructive: narrowingType || collationChanged
|
|
6268
|
+
destructive: narrowingType || collationChanged || dialect === "mysql" && before.nullable && !column.nullable
|
|
6137
6269
|
});
|
|
6138
6270
|
}
|
|
6139
6271
|
for (const [name] of previous.columns) {
|
|
@@ -6141,9 +6273,7 @@ function diffOneTable(params) {
|
|
|
6141
6273
|
buckets.dropColumn.push({ kind: "dropColumn", table, ...placement, column: name, destructive: true });
|
|
6142
6274
|
}
|
|
6143
6275
|
}
|
|
6144
|
-
|
|
6145
|
-
const pkAfter = current.pk.join(",");
|
|
6146
|
-
if (pkBefore !== pkAfter) {
|
|
6276
|
+
if (previous.pk.length !== current.pk.length || previous.pk.some((column, index) => column !== current.pk[index])) {
|
|
6147
6277
|
if (previous.pk.length > 0) {
|
|
6148
6278
|
buckets.dropPrimaryKey.push({ kind: "dropPrimaryKey", table, ...placement, destructive: false, ...previous.model.primaryKeyConstraintName === undefined ? {} : { constraintName: previous.model.primaryKeyConstraintName } });
|
|
6149
6279
|
}
|
|
@@ -6163,7 +6293,7 @@ function diffOneTable(params) {
|
|
|
6163
6293
|
if (before === expression)
|
|
6164
6294
|
continue;
|
|
6165
6295
|
if (before !== undefined && previous.model.checks?.some((check) => check.name === name && check.catalogExpression === true))
|
|
6166
|
-
throw new
|
|
6296
|
+
throw new VibeError31({ 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 } });
|
|
6167
6297
|
if (before !== undefined)
|
|
6168
6298
|
buckets.dropCheck.push({ kind: "dropCheck", table, ...placement, name, destructive: false });
|
|
6169
6299
|
buckets.addCheck.push({ kind: "addCheck", table, ...placement, name, expression, destructive: false });
|
|
@@ -6249,11 +6379,11 @@ function diffOneTable(params) {
|
|
|
6249
6379
|
}
|
|
6250
6380
|
|
|
6251
6381
|
// packages/migrate/src/ddl/render.ts
|
|
6252
|
-
import { VibeError as
|
|
6382
|
+
import { VibeError as VibeError35, sqlObjectKey as sqlObjectKey10 } from "@vibeorm/schema";
|
|
6253
6383
|
|
|
6254
6384
|
// packages/migrate/src/ddl/postgres.ts
|
|
6255
6385
|
import { renderExclusionSetup } from "@vibeorm/sql";
|
|
6256
|
-
import { VibeError as
|
|
6386
|
+
import { VibeError as VibeError32, assertSqlNamespace as assertSqlNamespace3, defaultPrimaryKeyName as defaultPrimaryKeyName3, implicitJoinTableObjectNames as implicitJoinTableObjectNames2, isEnumType as isEnumType7 } from "@vibeorm/schema";
|
|
6257
6387
|
import { postgresDialect as postgresDialect9, renderRlsPolicyPath, renderIndexColumns } from "@vibeorm/sql";
|
|
6258
6388
|
function renderPostgresSteps(params) {
|
|
6259
6389
|
refuseSameTransactionEnumUse({ steps: params.steps });
|
|
@@ -6290,7 +6420,7 @@ function refuseSameTransactionEnumUse(params) {
|
|
|
6290
6420
|
const found = use(field);
|
|
6291
6421
|
if (found === undefined)
|
|
6292
6422
|
continue;
|
|
6293
|
-
throw new
|
|
6423
|
+
throw new VibeError32({
|
|
6294
6424
|
code: "VIBE_MIGRATION",
|
|
6295
6425
|
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`,
|
|
6296
6426
|
meta: { enum: found.enumName, value: found.value, table: step.table, column: field.dbName ?? field.name, rule: "enum-value-used-in-adding-transaction" }
|
|
@@ -6364,7 +6494,7 @@ ${lines.join(`,
|
|
|
6364
6494
|
case "dropIndex":
|
|
6365
6495
|
if (step.constraintName !== undefined) {
|
|
6366
6496
|
if (step.table === undefined)
|
|
6367
|
-
throw new
|
|
6497
|
+
throw new VibeError32({ code: "VIBE_MIGRATION", message: "Constraint-backed index removal requires its owning table" });
|
|
6368
6498
|
return [`ALTER TABLE ${qualifiedName(step.table, step.namespace)} DROP CONSTRAINT ${q2(step.constraintName)};`];
|
|
6369
6499
|
}
|
|
6370
6500
|
return [`DROP INDEX ${qualifiedName(step.name, step.namespace)};`];
|
|
@@ -6429,7 +6559,7 @@ function columnDefSql(params) {
|
|
|
6429
6559
|
const { field } = params;
|
|
6430
6560
|
const name = q2(field.dbName ?? field.name);
|
|
6431
6561
|
if (field.default?.kind === "autoincrement" && !isIdentityColumn({ field })) {
|
|
6432
|
-
throw new
|
|
6562
|
+
throw new VibeError32({
|
|
6433
6563
|
code: "VIBE_MIGRATION",
|
|
6434
6564
|
message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
|
|
6435
6565
|
meta: { field: field.name }
|
|
@@ -6465,7 +6595,7 @@ function renderAlterColumn(params) {
|
|
|
6465
6595
|
}
|
|
6466
6596
|
if (step.changes.includes("default") || droppedDefault) {
|
|
6467
6597
|
if (step.to.default?.kind === "autoincrement") {
|
|
6468
|
-
throw new
|
|
6598
|
+
throw new VibeError32({
|
|
6469
6599
|
code: "VIBE_MIGRATION",
|
|
6470
6600
|
message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
|
|
6471
6601
|
meta: { table: step.table, column: step.column }
|
|
@@ -6485,7 +6615,7 @@ function q2(name) {
|
|
|
6485
6615
|
}
|
|
6486
6616
|
|
|
6487
6617
|
// packages/migrate/src/ddl/sqlite.ts
|
|
6488
|
-
import { VibeError as
|
|
6618
|
+
import { VibeError as VibeError33, implicitJoinTableObjectNames as implicitJoinTableObjectNames3, isEnumType as isEnumType8, unsupportedCapability as unsupportedCapability11 } from "@vibeorm/schema";
|
|
6489
6619
|
import { sqliteDialect as sqliteDialect3 } from "@vibeorm/sql";
|
|
6490
6620
|
|
|
6491
6621
|
// packages/migrate/src/ddl/drop-order.ts
|
|
@@ -6633,7 +6763,7 @@ function renderSqliteSteps(params) {
|
|
|
6633
6763
|
continue;
|
|
6634
6764
|
const awaitsNewParent = [...toNorm?.tables.get(table)?.fks.values() ?? []].some((fk) => createdTables.has(fk.refTable));
|
|
6635
6765
|
if (awaitsNewParent) {
|
|
6636
|
-
throw new
|
|
6766
|
+
throw new VibeError33({
|
|
6637
6767
|
code: "VIBE_MIGRATION",
|
|
6638
6768
|
message: `cannot order this sqlite migration safely: rebuilding "${table}" must happen BEFORE the dropped table(s) it references (${[
|
|
6639
6769
|
...droppedTables
|
|
@@ -6662,7 +6792,7 @@ function renderSqliteSteps(params) {
|
|
|
6662
6792
|
switch (step.kind) {
|
|
6663
6793
|
case "addExclusion":
|
|
6664
6794
|
case "dropExclusion":
|
|
6665
|
-
throw new
|
|
6795
|
+
throw new VibeError33({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
|
|
6666
6796
|
case "createEnum":
|
|
6667
6797
|
case "dropEnum":
|
|
6668
6798
|
case "addEnumValue":
|
|
@@ -6748,7 +6878,7 @@ function renderSqliteSteps(params) {
|
|
|
6748
6878
|
case "renameColumn": {
|
|
6749
6879
|
const renamed = step.kind === "renameTable" ? step.from : step.table;
|
|
6750
6880
|
if (rebuildSet.has(renamed) || step.kind === "renameTable" && rebuildSet.has(step.to)) {
|
|
6751
|
-
throw new
|
|
6881
|
+
throw new VibeError33({
|
|
6752
6882
|
code: "VIBE_MIGRATION",
|
|
6753
6883
|
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`,
|
|
6754
6884
|
meta: { reason: "rename-ambiguous", table: renamed, dialect: "sqlite" }
|
|
@@ -6802,7 +6932,7 @@ function classifyRebuilds(params) {
|
|
|
6802
6932
|
switch (step.kind) {
|
|
6803
6933
|
case "addExclusion":
|
|
6804
6934
|
case "dropExclusion":
|
|
6805
|
-
throw new
|
|
6935
|
+
throw new VibeError33({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "sqlite", feature: "exclusionConstraints" } });
|
|
6806
6936
|
case "alterColumn":
|
|
6807
6937
|
case "dropForeignKey":
|
|
6808
6938
|
case "addPrimaryKey":
|
|
@@ -6863,7 +6993,7 @@ function renderTableRebuild(params) {
|
|
|
6863
6993
|
const source = from.tables.get(table);
|
|
6864
6994
|
const target = to.tables.get(table);
|
|
6865
6995
|
if (source === undefined || target === undefined) {
|
|
6866
|
-
throw new
|
|
6996
|
+
throw new VibeError33({
|
|
6867
6997
|
code: "VIBE_MIGRATION",
|
|
6868
6998
|
message: `cannot rebuild "${table}" \u2014 the table is missing from the ${source === undefined ? "from" : "to"} schema`,
|
|
6869
6999
|
meta: { table }
|
|
@@ -6998,7 +7128,7 @@ function columnDefSql2(params) {
|
|
|
6998
7128
|
const { field, constraintTable, enums, inlinePkAutoincrement } = params;
|
|
6999
7129
|
const name = field.dbName ?? field.name;
|
|
7000
7130
|
if (field.default?.kind === "autoincrement" && !inlinePkAutoincrement) {
|
|
7001
|
-
throw new
|
|
7131
|
+
throw new VibeError33({
|
|
7002
7132
|
code: "VIBE_MIGRATION",
|
|
7003
7133
|
message: `autoincrement on sqlite requires "${field.name}" to be the single-column Int or BigInt primary key`,
|
|
7004
7134
|
meta: { field: field.name }
|
|
@@ -7017,7 +7147,7 @@ function columnDefSql2(params) {
|
|
|
7017
7147
|
const enumName = field.type.enum;
|
|
7018
7148
|
const values = resolveEnumValues({ name: enumName, enums });
|
|
7019
7149
|
if (values === undefined) {
|
|
7020
|
-
throw new
|
|
7150
|
+
throw new VibeError33({
|
|
7021
7151
|
code: "VIBE_MIGRATION",
|
|
7022
7152
|
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`,
|
|
7023
7153
|
meta: { field: field.name, enum: enumName }
|
|
@@ -7052,7 +7182,7 @@ function requireSchemas(params) {
|
|
|
7052
7182
|
const { step, fromNorm, toNorm } = params;
|
|
7053
7183
|
if (fromNorm !== undefined && toNorm !== undefined)
|
|
7054
7184
|
return;
|
|
7055
|
-
throw new
|
|
7185
|
+
throw new VibeError33({
|
|
7056
7186
|
code: "VIBE_MIGRATION",
|
|
7057
7187
|
message: `sqlite step "${describeStep({ step })}" requires a table rebuild \u2014 renderSteps needs the { from, to } schemas to construct it`,
|
|
7058
7188
|
meta: { step: step.kind }
|
|
@@ -7063,7 +7193,7 @@ function q3(name) {
|
|
|
7063
7193
|
}
|
|
7064
7194
|
|
|
7065
7195
|
// packages/migrate/src/ddl/mysql.ts
|
|
7066
|
-
import { VibeError as
|
|
7196
|
+
import { VibeError as VibeError34, implicitJoinTableObjectNames as implicitJoinTableObjectNames4, unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
|
|
7067
7197
|
import { mysqlDialect as mysqlDialect3 } from "@vibeorm/sql";
|
|
7068
7198
|
function renderMysqlSteps(params) {
|
|
7069
7199
|
for (const step of params.steps) {
|
|
@@ -7211,12 +7341,12 @@ ${lines.join(`,
|
|
|
7211
7341
|
];
|
|
7212
7342
|
case "addExclusion":
|
|
7213
7343
|
case "dropExclusion":
|
|
7214
|
-
throw new
|
|
7344
|
+
throw new VibeError34({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: "Exclusion constraints require PostgreSQL", meta: { dialect: "mysql", feature: "exclusionConstraints" } });
|
|
7215
7345
|
case "dropCheck":
|
|
7216
7346
|
return [`ALTER TABLE ${q4(step.table)} DROP CHECK ${q4(step.name)};`];
|
|
7217
7347
|
case "dropIndex": {
|
|
7218
7348
|
if (step.table === undefined) {
|
|
7219
|
-
throw new
|
|
7349
|
+
throw new VibeError34({
|
|
7220
7350
|
code: "VIBE_MIGRATION",
|
|
7221
7351
|
message: `dropIndex "${step.name}" carries no table \u2014 mysql's DROP INDEX requires one`,
|
|
7222
7352
|
meta: { index: step.name }
|
|
@@ -7246,8 +7376,8 @@ ${lines.join(`,
|
|
|
7246
7376
|
case "dropForeignKey":
|
|
7247
7377
|
return [`ALTER TABLE ${q4(step.table)} DROP FOREIGN KEY ${q4(step.constraintName)};`];
|
|
7248
7378
|
case "createJoinTable": {
|
|
7249
|
-
const aType =
|
|
7250
|
-
const bType =
|
|
7379
|
+
const aType = joinColumnTypeSql({ field: step.a.field, enums });
|
|
7380
|
+
const bType = joinColumnTypeSql({ field: step.b.field, enums });
|
|
7251
7381
|
const names = implicitJoinTableObjectNames4({ table: step.table });
|
|
7252
7382
|
return [
|
|
7253
7383
|
`CREATE TABLE ${q4(step.table)} (
|
|
@@ -7268,12 +7398,17 @@ ${lines.join(`,
|
|
|
7268
7398
|
return [`ALTER TABLE ${q4(step.table)} DROP PRIMARY KEY;`];
|
|
7269
7399
|
}
|
|
7270
7400
|
}
|
|
7401
|
+
function joinColumnTypeSql(params) {
|
|
7402
|
+
const { field, enums } = params;
|
|
7403
|
+
const type = mysqlColumnTypeSql({ field: { ...field, isList: false }, enums });
|
|
7404
|
+
return field.collation === undefined ? type : `${type} COLLATE ${q4(field.collation)}`;
|
|
7405
|
+
}
|
|
7271
7406
|
function columnDefSql3(params) {
|
|
7272
7407
|
const { field, enums } = params;
|
|
7273
7408
|
const name = q4(field.dbName ?? field.name);
|
|
7274
7409
|
const autoincrement = field.default?.kind === "autoincrement";
|
|
7275
7410
|
if (autoincrement && field.type !== "Int" && field.type !== "BigInt") {
|
|
7276
|
-
throw new
|
|
7411
|
+
throw new VibeError34({
|
|
7277
7412
|
code: "VIBE_MIGRATION",
|
|
7278
7413
|
message: `autoincrement default on "${field.name}" requires a non-list Int or BigInt column`,
|
|
7279
7414
|
meta: { field: field.name }
|
|
@@ -7295,7 +7430,7 @@ function renderAlterColumn2(params) {
|
|
|
7295
7430
|
const { step, enums } = params;
|
|
7296
7431
|
assertEnumStringTransition({ from: step.from, to: step.to, dialect: "mysql", table: step.table, column: step.column });
|
|
7297
7432
|
if (step.to.default?.kind === "autoincrement" && step.from.default?.kind !== "autoincrement") {
|
|
7298
|
-
throw new
|
|
7433
|
+
throw new VibeError34({
|
|
7299
7434
|
code: "VIBE_MIGRATION",
|
|
7300
7435
|
message: `cannot add autoincrement to existing column "${step.table}"."${step.column}" \u2014 drop and recreate the column instead`,
|
|
7301
7436
|
meta: { table: step.table, column: step.column }
|
|
@@ -7335,24 +7470,24 @@ function declaredSqlitePreservation(params) {
|
|
|
7335
7470
|
continue;
|
|
7336
7471
|
const facts = object.expected?.facts;
|
|
7337
7472
|
if (facts === undefined)
|
|
7338
|
-
throw new
|
|
7473
|
+
throw new VibeError35({ code: "VIBE_MIGRATION", message: "SQLite rebuild requires independently declared preservation definitions", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
7339
7474
|
if (identity.kind === "trigger" || identity.kind === "index") {
|
|
7340
7475
|
if (identity.kind === "index" && to.models.some((model) => (model.dbName ?? model.name) === identity.table.name && model.indexes.some((index) => index.name === identity.name)))
|
|
7341
7476
|
continue;
|
|
7342
7477
|
if (typeof facts.definition !== "string")
|
|
7343
|
-
throw new
|
|
7478
|
+
throw new VibeError35({ code: "VIBE_MIGRATION", message: "Missing SQLite restoration SQL", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
7344
7479
|
const statements = restore.get(identity.table.name) ?? [];
|
|
7345
7480
|
statements.push(facts.definition);
|
|
7346
7481
|
restore.set(identity.table.name, statements);
|
|
7347
7482
|
} else if (identity.kind === "column" || identity.kind === "generatedColumn") {
|
|
7348
7483
|
if (typeof facts.columnDefinition !== "string")
|
|
7349
|
-
throw new
|
|
7484
|
+
throw new VibeError35({ code: "VIBE_MIGRATION", message: "Missing SQLite column clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
7350
7485
|
const fields = columns.get(identity.table.name) ?? new Map;
|
|
7351
7486
|
fields.set(identity.column, { sql: facts.columnDefinition, generated: Number(facts.generated) > 1 });
|
|
7352
7487
|
columns.set(identity.table.name, fields);
|
|
7353
7488
|
} else if (identity.kind === "columnDefault") {
|
|
7354
7489
|
if (facts.definition !== null && typeof facts.definition !== "string")
|
|
7355
|
-
throw new
|
|
7490
|
+
throw new VibeError35({ code: "VIBE_MIGRATION", message: "Missing SQLite default clause", meta: { reason: "sqlite-preservation-unavailable", identity } });
|
|
7356
7491
|
const expression = facts.definition;
|
|
7357
7492
|
to = { ...to, models: to.models.map((model) => (model.dbName ?? model.name) !== identity.table.name ? model : { ...model, fields: model.fields.map((field) => {
|
|
7358
7493
|
if ((field.dbName ?? field.name) !== identity.column)
|
|
@@ -7389,7 +7524,7 @@ function renderSteps(params) {
|
|
|
7389
7524
|
}
|
|
7390
7525
|
|
|
7391
7526
|
// packages/migrate/src/extensions.ts
|
|
7392
|
-
import { extensionDeclarations } from "@vibeorm/schema";
|
|
7527
|
+
import { extensionDeclarations, providerToDialect as providerToDialect4, qualifiedSqlName as qualifiedSqlName10, SQL_NAMESPACES } from "@vibeorm/schema";
|
|
7393
7528
|
function collectExtensionArtifacts(params) {
|
|
7394
7529
|
const { schema, dialect, extensions } = params;
|
|
7395
7530
|
const setup = [];
|
|
@@ -7456,6 +7591,10 @@ function stripExtensionArtifacts(params) {
|
|
|
7456
7591
|
const models = [];
|
|
7457
7592
|
for (const model of schema.models) {
|
|
7458
7593
|
const tableName = model.dbName ?? model.name;
|
|
7594
|
+
if (providerToDialect4({ provider: schema.datasource.provider }) === "postgres" && qualifiedSqlName10({ name: tableName, namespace: model.namespace }).namespace !== SQL_NAMESPACES.postgres) {
|
|
7595
|
+
models.push(model);
|
|
7596
|
+
continue;
|
|
7597
|
+
}
|
|
7459
7598
|
if (tables.exact.has(tableName) || tables.prefixes.some((prefix) => tableName.startsWith(prefix))) {
|
|
7460
7599
|
continue;
|
|
7461
7600
|
}
|
|
@@ -7479,6 +7618,8 @@ function planMissingArtifacts(params) {
|
|
|
7479
7618
|
return [];
|
|
7480
7619
|
const modelByTable = new Map;
|
|
7481
7620
|
for (const model of introspected.models) {
|
|
7621
|
+
if (providerToDialect4({ provider: introspected.datasource.provider }) === "postgres" && qualifiedSqlName10({ name: model.dbName ?? model.name, namespace: model.namespace }).namespace !== SQL_NAMESPACES.postgres)
|
|
7622
|
+
continue;
|
|
7482
7623
|
modelByTable.set(model.dbName ?? model.name, model);
|
|
7483
7624
|
}
|
|
7484
7625
|
return artifacts.filter((artifact) => {
|
|
@@ -7637,10 +7778,10 @@ function assertNativeMigrationMetadata(params) {
|
|
|
7637
7778
|
if (!native)
|
|
7638
7779
|
return;
|
|
7639
7780
|
if (!DIALECT_CAPABILITIES3[params.dialect].nativeRowLevelSecurity) {
|
|
7640
|
-
throw new
|
|
7781
|
+
throw new VibeError36({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Native RLS migrations are unsupported on ${params.dialect}`, meta: { feature: "nativeRowLevelSecurity", dialect: params.dialect } });
|
|
7641
7782
|
}
|
|
7642
7783
|
if (params.migration.rls === undefined) {
|
|
7643
|
-
throw new
|
|
7784
|
+
throw new VibeError36({ 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 } });
|
|
7644
7785
|
}
|
|
7645
7786
|
}
|
|
7646
7787
|
var PG_ADVISORY_LOCK_KEY = 1447645253;
|
|
@@ -7721,12 +7862,12 @@ function generateMigration(params) {
|
|
|
7721
7862
|
}),
|
|
7722
7863
|
...orderedMigrationObjects({ objects: to.migrationObjects ?? [] }).flatMap((object) => {
|
|
7723
7864
|
if (object.dialect !== dialect)
|
|
7724
|
-
throw new
|
|
7865
|
+
throw new VibeError36({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Migration object ${object.id} requires ${object.dialect}` });
|
|
7725
7866
|
const previous = (params.desiredState?.from.schema.migrationObjects ?? from.migrationObjects)?.find((entry) => entry.id === object.id);
|
|
7726
7867
|
if (previous === undefined)
|
|
7727
7868
|
return [...object.create];
|
|
7728
7869
|
if (JSON.stringify(previous) !== JSON.stringify(object))
|
|
7729
|
-
throw new
|
|
7870
|
+
throw new VibeError36({ 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 } });
|
|
7730
7871
|
return [];
|
|
7731
7872
|
})
|
|
7732
7873
|
];
|
|
@@ -7752,8 +7893,8 @@ async function withMigrationLock(params) {
|
|
|
7752
7893
|
}, (cause) => releaseLock({ executor, dialect: params.dialect }).then(() => {
|
|
7753
7894
|
throw cause;
|
|
7754
7895
|
}, (cleanupCause) => {
|
|
7755
|
-
if (cause instanceof
|
|
7756
|
-
throw new
|
|
7896
|
+
if (cause instanceof VibeError36)
|
|
7897
|
+
throw new VibeError36({ code: cause.code, message: cause.message, meta: { ...cause.meta, cleanupCause }, cause });
|
|
7757
7898
|
throw cause;
|
|
7758
7899
|
}));
|
|
7759
7900
|
};
|
|
@@ -7783,11 +7924,11 @@ function assertMigrationMetadata(params) {
|
|
|
7783
7924
|
assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
|
|
7784
7925
|
}
|
|
7785
7926
|
if (migration.desiredStatePending === true)
|
|
7786
|
-
throw new
|
|
7927
|
+
throw new VibeError36({ 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 } });
|
|
7787
7928
|
if (migration.desiredState !== undefined) {
|
|
7788
7929
|
assertDesiredState({ value: migration.desiredState, migration: migration.name });
|
|
7789
7930
|
if (migration.desiredState.dialect !== dialect)
|
|
7790
|
-
throw new
|
|
7931
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch", migration: migration.name } });
|
|
7791
7932
|
}
|
|
7792
7933
|
assertNativeMigrationMetadata({ migration, sql: migration.sql, dialect });
|
|
7793
7934
|
assertExecutable({ migration, execution: migration.execution, sql: migration.sql, dialect });
|
|
@@ -7797,7 +7938,7 @@ async function applyMigrations(params) {
|
|
|
7797
7938
|
const dialect = params.dialect ?? "postgres";
|
|
7798
7939
|
const trustedReplay = isShadowReplayExecutor({ executor });
|
|
7799
7940
|
if (params.history !== undefined && params.history !== "complete" && params.history !== "subset")
|
|
7800
|
-
throw new
|
|
7941
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Unknown migration history validation mode", meta: { reason: "history-mode-invalid" } });
|
|
7801
7942
|
if (params.history === "complete")
|
|
7802
7943
|
assertDistinctMigrationNames({ migrations });
|
|
7803
7944
|
for (const migration of migrations)
|
|
@@ -7813,7 +7954,7 @@ async function applyMigrations(params) {
|
|
|
7813
7954
|
assertModuleUpgradeHistory({ migrations, records: moduleRecords });
|
|
7814
7955
|
const namespace = await migrationNamespace({ executor, dialect });
|
|
7815
7956
|
if (dialect !== "postgres" && migrations.some((migration) => migration.moduleOrigin !== undefined && moduleNamespace({ origin: migration.moduleOrigin }) !== namespace))
|
|
7816
|
-
throw new
|
|
7957
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Module placement differs from the active database namespace", meta: { reason: "module-placement-mismatch" } });
|
|
7817
7958
|
}
|
|
7818
7959
|
if (params.history === "complete")
|
|
7819
7960
|
assertCompleteHistory({ migrations, recorded, dialect });
|
|
@@ -7824,7 +7965,7 @@ async function applyMigrations(params) {
|
|
|
7824
7965
|
});
|
|
7825
7966
|
const adopted = pending.find((migration) => migration.moduleAdoption !== undefined);
|
|
7826
7967
|
if (adopted !== undefined && !trustedReplay)
|
|
7827
|
-
throw new
|
|
7968
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "An adopted source must be recorded by the verified adoption workflow, never executed by ordinary apply", meta: { reason: "module-adoption-recording-required", migration: adopted.name } });
|
|
7828
7969
|
if (external !== undefined && !trustedReplay)
|
|
7829
7970
|
for (const migration of pending)
|
|
7830
7971
|
await assertExternalMigrationMutation({ executor, migration, dialect, namespace: await migrationNamespace({ executor, dialect }), external });
|
|
@@ -7840,7 +7981,7 @@ async function applyMigrations(params) {
|
|
|
7840
7981
|
await assertLiveModuleOwnership({ executor, origin: migration.moduleOrigin, resume: stored !== undefined });
|
|
7841
7982
|
if (stored !== undefined) {
|
|
7842
7983
|
if (stored.statementIndex < 0) {
|
|
7843
|
-
throw new
|
|
7984
|
+
throw new VibeError36({
|
|
7844
7985
|
code: "VIBE_MIGRATION",
|
|
7845
7986
|
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`,
|
|
7846
7987
|
meta: { migration: migration.name, statementIndex: stored.statementIndex }
|
|
@@ -7998,7 +8139,7 @@ async function repairMigration(params) {
|
|
|
7998
8139
|
const { executor, name, dialect } = params;
|
|
7999
8140
|
const moduleRecord = (await readModuleJournal({ executor, dialect })).get(name);
|
|
8000
8141
|
if (moduleRecord?.entry !== undefined)
|
|
8001
|
-
throw new
|
|
8142
|
+
throw new VibeError36({ 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 } });
|
|
8002
8143
|
await ensureMigrationsTable({ executor, dialect });
|
|
8003
8144
|
if (params.as === "applied") {
|
|
8004
8145
|
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})`;
|
|
@@ -8020,7 +8161,7 @@ async function rollbackMigration(params) {
|
|
|
8020
8161
|
const stored = assertRollbackRecord({ migration, stored: recorded.get(migration.name), dialect });
|
|
8021
8162
|
const newest = [...recorded.keys()].at(-1);
|
|
8022
8163
|
if (newest !== migration.name)
|
|
8023
|
-
throw new
|
|
8164
|
+
throw new VibeError36({ 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 } });
|
|
8024
8165
|
await validateRollbackRecovery({ executor, migration, downSql, stored, dialect });
|
|
8025
8166
|
await executeRollback({ ...params, executor, dialect, downSql, stored });
|
|
8026
8167
|
} });
|
|
@@ -8038,7 +8179,7 @@ async function rollbackMigrations(params) {
|
|
|
8038
8179
|
const target = journal.findIndex((row) => row.name === params.to);
|
|
8039
8180
|
const row = journal[target];
|
|
8040
8181
|
if (row?.state !== "applied")
|
|
8041
|
-
throw new
|
|
8182
|
+
throw new VibeError36({ 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 } });
|
|
8042
8183
|
assertMigrationMetadata({ migration: byName.get(row.name), dialect });
|
|
8043
8184
|
start = target + 1;
|
|
8044
8185
|
}
|
|
@@ -8046,7 +8187,7 @@ async function rollbackMigrations(params) {
|
|
|
8046
8187
|
for (const row of journal.slice(start).reverse()) {
|
|
8047
8188
|
const migration = byName.get(row.name);
|
|
8048
8189
|
if (migration === undefined)
|
|
8049
|
-
throw new
|
|
8190
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Cannot roll back "${row.name}" without its migration file`, meta: { reason: "rollback-file-missing", migration: row.name } });
|
|
8050
8191
|
const stored = assertRollbackRecord({ migration, stored: recorded.get(row.name), dialect });
|
|
8051
8192
|
const downSql = validateRollbackFile({ ...params, migration, dialect });
|
|
8052
8193
|
plan.push({ migration, downSql, stored });
|
|
@@ -8065,21 +8206,21 @@ function validateRollbackFile(params) {
|
|
|
8065
8206
|
const { migration, dialect } = params;
|
|
8066
8207
|
assertModuleAdoption({ migration });
|
|
8067
8208
|
if (migration.moduleAdoption !== undefined)
|
|
8068
|
-
throw new
|
|
8209
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Reversal across verified module adoption is refused; it would execute destructive source down SQL against pre-existing data", meta: { reason: "module-adoption-reverse-refused", migration: migration.name } });
|
|
8069
8210
|
if (migration.desiredStatePending === true)
|
|
8070
|
-
throw new
|
|
8211
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Unfinalized desired state cannot be rolled back", meta: { reason: "desired-state-unfinalized", migration: migration.name } });
|
|
8071
8212
|
if (migration.moduleOrigin !== undefined) {
|
|
8072
8213
|
assertMaterializedModuleMigration({ migration });
|
|
8073
8214
|
assertModuleOwnership({ origin: migration.moduleOrigin, desiredState: migration.desiredState });
|
|
8074
8215
|
}
|
|
8075
8216
|
const downSql = migration.downSql;
|
|
8076
8217
|
if (downSql === undefined)
|
|
8077
|
-
throw new
|
|
8218
|
+
throw new VibeError36({ 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 } });
|
|
8078
8219
|
assertProbeMode({ dialect, probe: params.probe });
|
|
8079
8220
|
if (migration.desiredState !== undefined) {
|
|
8080
8221
|
assertDesiredState({ value: migration.desiredState, migration: migration.name });
|
|
8081
8222
|
if (migration.desiredState.dialect !== dialect)
|
|
8082
|
-
throw new
|
|
8223
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Migration desired-state dialect mismatch", meta: { reason: "desired-state-dialect-mismatch" } });
|
|
8083
8224
|
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 } });
|
|
8084
8225
|
}
|
|
8085
8226
|
assertNativeMigrationMetadata({ migration, sql: downSql, dialect });
|
|
@@ -8092,10 +8233,10 @@ function validateRollbackFile(params) {
|
|
|
8092
8233
|
function assertRollbackRecord(params) {
|
|
8093
8234
|
const { migration, stored, dialect } = params;
|
|
8094
8235
|
if (stored === undefined)
|
|
8095
|
-
throw new
|
|
8236
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `migration "${migration.name}" is not recorded as applied; nothing to roll back`, meta: { migration: migration.name } });
|
|
8096
8237
|
assertChecksumMatches({ migration, stored: stored.checksum });
|
|
8097
8238
|
if (stored.statementIndex >= 0 && (!isCompleteRecord({ dialect, migration, stored }) || stored.error !== undefined))
|
|
8098
|
-
throw new
|
|
8239
|
+
throw new VibeError36({ 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 } });
|
|
8099
8240
|
return stored;
|
|
8100
8241
|
}
|
|
8101
8242
|
async function validateRollbackRecovery(params) {
|
|
@@ -8104,7 +8245,7 @@ async function validateRollbackRecovery(params) {
|
|
|
8104
8245
|
return;
|
|
8105
8246
|
const start = -stored.statementIndex - 1;
|
|
8106
8247
|
if (start > downSql.length)
|
|
8107
|
-
throw new
|
|
8248
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: "Rollback progress exceeds its statement count", meta: { reason: "rollback-progress-invalid", migration: migration.name } });
|
|
8108
8249
|
if (dialect === "sqlite" && usesSelfManagedTransactions({ sql: downSql }))
|
|
8109
8250
|
assertSelfManagedResumeSafe({ sql: downSql, start, migration: migration.name, direction: "down" });
|
|
8110
8251
|
if (!marksInflight({ dialect, execution: migration.downExecution }) || start >= downSql.length)
|
|
@@ -8170,7 +8311,7 @@ async function runTransactionalDown(params) {
|
|
|
8170
8311
|
} else {
|
|
8171
8312
|
await cleanupSelfManagedFailure({ executor });
|
|
8172
8313
|
}
|
|
8173
|
-
throw new
|
|
8314
|
+
throw new VibeError36({
|
|
8174
8315
|
code: "VIBE_MIGRATION",
|
|
8175
8316
|
message: `rollback of "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
8176
8317
|
meta: { migration: migration.name, ...timeoutDiagnostic({ error }) },
|
|
@@ -8253,7 +8394,7 @@ async function runNonTransactionalDown(params) {
|
|
|
8253
8394
|
values: [marks ? `${INFLIGHT_PREFIX}${index}` : error instanceof Error ? error.message : String(error), migration.name]
|
|
8254
8395
|
});
|
|
8255
8396
|
} catch {}
|
|
8256
|
-
throw new
|
|
8397
|
+
throw new VibeError36({
|
|
8257
8398
|
code: "VIBE_MIGRATION",
|
|
8258
8399
|
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."}`,
|
|
8259
8400
|
meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeoutDiagnostic({ error }) },
|
|
@@ -8315,7 +8456,7 @@ async function runTransactional(params) {
|
|
|
8315
8456
|
} else {
|
|
8316
8457
|
await cleanupSelfManagedFailure({ executor });
|
|
8317
8458
|
}
|
|
8318
|
-
throw new
|
|
8459
|
+
throw new VibeError36({
|
|
8319
8460
|
code: "VIBE_MIGRATION",
|
|
8320
8461
|
message: `migration "${migration.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
8321
8462
|
meta: { migration: migration.name },
|
|
@@ -8411,7 +8552,7 @@ async function runNonTransactional(params) {
|
|
|
8411
8552
|
} catch {}
|
|
8412
8553
|
const sqlState = sqlStateOf(error);
|
|
8413
8554
|
const timeout = sqlState === "55P03" ? "lock-timeout" : sqlState === "57014" ? "statement-timeout" : undefined;
|
|
8414
|
-
throw new
|
|
8555
|
+
throw new VibeError36({
|
|
8415
8556
|
code: "VIBE_MIGRATION",
|
|
8416
8557
|
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."}`,
|
|
8417
8558
|
meta: { migration: migration.name, statementIndex: index, ...marks ? { outcome: "unknown" } : {}, ...timeout === undefined ? {} : { reason: timeout, sqlState } },
|
|
@@ -8439,7 +8580,7 @@ async function acquireLock(params) {
|
|
|
8439
8580
|
});
|
|
8440
8581
|
const acquired = Number(rows[0]?.vibe_lock) === 1;
|
|
8441
8582
|
if (!acquired) {
|
|
8442
|
-
throw new
|
|
8583
|
+
throw new VibeError36({
|
|
8443
8584
|
code: "VIBE_MIGRATION",
|
|
8444
8585
|
message: `could not acquire the migration lock ('${MYSQL_LOCK_NAME}') within ${MYSQL_LOCK_TIMEOUT_SECONDS}s \u2014 another migration run is in progress`,
|
|
8445
8586
|
meta: { lock: MYSQL_LOCK_NAME }
|
|
@@ -8474,7 +8615,7 @@ function uncertainStatement(params) {
|
|
|
8474
8615
|
const subject = direction === "down" ? `rollback of "${migration}"` : `migration "${migration}"`;
|
|
8475
8616
|
const position = `${direction === "down" ? "down statement" : "statement"} ${index + 1} of ${statements.length}`;
|
|
8476
8617
|
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.";
|
|
8477
|
-
return new
|
|
8618
|
+
return new VibeError36({
|
|
8478
8619
|
code: "VIBE_MIGRATION",
|
|
8479
8620
|
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)}`,
|
|
8480
8621
|
meta: {
|
|
@@ -8492,7 +8633,7 @@ function assertExecutable(params) {
|
|
|
8492
8633
|
if (execution === undefined || isDefaultExecution({ execution }))
|
|
8493
8634
|
return;
|
|
8494
8635
|
if (dialect !== "postgres") {
|
|
8495
|
-
throw new
|
|
8636
|
+
throw new VibeError36({
|
|
8496
8637
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8497
8638
|
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`,
|
|
8498
8639
|
meta: { reason: "nontransactional-unsupported", dialect, migration: migration.name, feature: "nontransactionalMigrations" }
|
|
@@ -8568,7 +8709,7 @@ function assertChecksumMatches(params) {
|
|
|
8568
8709
|
const actual = checksumOf({ migration });
|
|
8569
8710
|
if (stored === actual)
|
|
8570
8711
|
return;
|
|
8571
|
-
throw new
|
|
8712
|
+
throw new VibeError36({
|
|
8572
8713
|
code: "VIBE_MIGRATION",
|
|
8573
8714
|
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)`,
|
|
8574
8715
|
meta: { migration: migration.name, recorded: stored, current: actual }
|
|
@@ -8579,7 +8720,7 @@ function assertResumable(params) {
|
|
|
8579
8720
|
const actual = checksumOf({ migration });
|
|
8580
8721
|
if (stored.checksum === actual)
|
|
8581
8722
|
return;
|
|
8582
|
-
throw new
|
|
8723
|
+
throw new VibeError36({
|
|
8583
8724
|
code: "VIBE_MIGRATION",
|
|
8584
8725
|
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`,
|
|
8585
8726
|
meta: {
|
|
@@ -8599,7 +8740,7 @@ function assertDistinctMigrationNames(params) {
|
|
|
8599
8740
|
const names = new Set;
|
|
8600
8741
|
for (const migration of params.migrations) {
|
|
8601
8742
|
if (names.has(migration.name))
|
|
8602
|
-
throw new
|
|
8743
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Duplicate migration file identity "${migration.name}"`, meta: { reason: "history-duplicate-file", migration: migration.name } });
|
|
8603
8744
|
names.add(migration.name);
|
|
8604
8745
|
}
|
|
8605
8746
|
}
|
|
@@ -8607,15 +8748,15 @@ function assertCompleteHistory(params) {
|
|
|
8607
8748
|
const byName = new Map(params.migrations.map((migration) => [migration.name, migration]));
|
|
8608
8749
|
for (const name of params.recorded.keys()) {
|
|
8609
8750
|
if (!byName.has(name))
|
|
8610
|
-
throw new
|
|
8751
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Recorded migration "${name}" has no file in the complete chain`, meta: { reason: "history-unknown-file", migration: name } });
|
|
8611
8752
|
}
|
|
8612
8753
|
let index = 0;
|
|
8613
8754
|
for (const [name, stored] of params.recorded) {
|
|
8614
8755
|
const migration = params.migrations[index];
|
|
8615
8756
|
if (migration?.name !== name)
|
|
8616
|
-
throw new
|
|
8757
|
+
throw new VibeError36({ 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 } });
|
|
8617
8758
|
if (stored.statementIndex >= 0 && !isCompleteRecord({ dialect: params.dialect, migration, stored }) && index !== params.recorded.size - 1)
|
|
8618
|
-
throw new
|
|
8759
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Partial migration "${name}" precedes later recorded work`, meta: { reason: "history-partial-not-last", migration: name } });
|
|
8619
8760
|
index++;
|
|
8620
8761
|
}
|
|
8621
8762
|
}
|
|
@@ -8624,16 +8765,16 @@ async function assertApplyHistory(params) {
|
|
|
8624
8765
|
const byName = new Map(migrations.map((migration) => [migration.name, migration]));
|
|
8625
8766
|
for (const [name, stored] of recorded) {
|
|
8626
8767
|
if (stored.statementIndex < 0) {
|
|
8627
|
-
throw new
|
|
8768
|
+
throw new VibeError36({ 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 } });
|
|
8628
8769
|
}
|
|
8629
8770
|
const migration = byName.get(name);
|
|
8630
8771
|
if (migration === undefined) {
|
|
8631
8772
|
if (stored.error !== undefined)
|
|
8632
|
-
throw new
|
|
8773
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error but no supplied file`, meta: { reason: "history-unknown-dirty", migration: name } });
|
|
8633
8774
|
continue;
|
|
8634
8775
|
}
|
|
8635
8776
|
if (isCompleteRecord({ dialect, migration, stored }) && stored.error !== undefined)
|
|
8636
|
-
throw new
|
|
8777
|
+
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Migration "${name}" has an unresolved journal error`, meta: { reason: "history-dirty", migration: name } });
|
|
8637
8778
|
if (isCompleteRecord({ dialect, migration, stored }))
|
|
8638
8779
|
assertChecksumMatches({ migration, stored: stored.checksum });
|
|
8639
8780
|
else
|
|
@@ -8669,7 +8810,7 @@ function absent(error) {
|
|
|
8669
8810
|
return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
8670
8811
|
}
|
|
8671
8812
|
function failure(params) {
|
|
8672
|
-
return new
|
|
8813
|
+
return new VibeError37({ code: "VIBE_MIGRATION", message: `Cannot read migration file ${params.path}`, meta: { reason: "migration-file-unreadable", path: params.path }, cause: params.cause });
|
|
8673
8814
|
}
|
|
8674
8815
|
async function optionalFile(params) {
|
|
8675
8816
|
return readFile(params.path, "utf-8").catch((cause) => {
|
|
@@ -8683,13 +8824,13 @@ function rlsMetadata(params) {
|
|
|
8683
8824
|
const metadata = JSON.parse(params.text);
|
|
8684
8825
|
const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
8685
8826
|
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 ?? [] })) {
|
|
8686
|
-
throw new
|
|
8827
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: "Invalid native RLS metadata" });
|
|
8687
8828
|
}
|
|
8688
8829
|
assertValidSchemaIR4({ schema: metadata.rls.from });
|
|
8689
8830
|
assertValidSchemaIR4({ schema: metadata.rls.to });
|
|
8690
8831
|
return { ...params.migration, rls: metadata.rls, destructive: metadata.destructive, securityChanges: metadata.securityChanges, downSecurityChanges: metadata.downSecurityChanges };
|
|
8691
8832
|
}).catch((cause) => {
|
|
8692
|
-
throw new
|
|
8833
|
+
throw new VibeError37({ code: "VIBE_MIGRATION", message: `Invalid native RLS metadata for migration "${params.migration.name}"`, meta: { reason: "rls-migration-metadata-invalid", migration: params.migration.name }, cause });
|
|
8693
8834
|
});
|
|
8694
8835
|
}
|
|
8695
8836
|
async function loadMigrationFiles(params) {
|
|
@@ -8721,10 +8862,7 @@ async function loadMigrationFiles(params) {
|
|
|
8721
8862
|
migration = { ...migration, ...parseExecutionSidecar({ text: execution, migration: name, statements: migration.sql.length, downStatements: migration.downSql?.length ?? 0 }) };
|
|
8722
8863
|
const desired = await optionalFile({ path: join(directory, DESIRED_STATE_METADATA.filename) });
|
|
8723
8864
|
if (desired !== undefined) {
|
|
8724
|
-
|
|
8725
|
-
throw new VibeError36({ code: "VIBE_MIGRATION", message: `Malformed desired state for ${name}`, meta: { reason: "desired-state-invalid", migration: name }, cause });
|
|
8726
|
-
});
|
|
8727
|
-
migration = pending ? { ...migration, desiredStatePending: true } : { ...migration, desiredState: parseDesiredStateSidecar({ text: desired, migration: name }) };
|
|
8865
|
+
migration = { ...migration, ...loadDesiredStateSidecar({ text: desired, migration: name }) };
|
|
8728
8866
|
}
|
|
8729
8867
|
const origin = await optionalFile({ path: join(directory, MODULE_ORIGIN_METADATA.filename) });
|
|
8730
8868
|
if (origin !== undefined)
|
|
@@ -8743,4 +8881,4 @@ export {
|
|
|
8743
8881
|
splitSqlStatements
|
|
8744
8882
|
};
|
|
8745
8883
|
|
|
8746
|
-
//# debugId=
|
|
8884
|
+
//# debugId=73DFB0227E02518164756E2164756E21
|