@prisma/orm-family-sql 8.0.0-rc.16-dev.6 → 8.0.0-rc.16-dev.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{control-DxvOSmwF.mjs → control-BRbds4vn.mjs} +627 -123
- package/dist/control-BRbds4vn.mjs.map +1 -0
- package/dist/{control-DCt08EpW.d.mts → control-BkyDhucB.d.mts} +262 -56
- package/dist/control-BkyDhucB.d.mts.map +1 -0
- package/dist/{diff-3tfqwWS7.d.mts → diff-DMOqiT2h.d.mts} +2 -2
- package/dist/{diff-3tfqwWS7.d.mts.map → diff-DMOqiT2h.d.mts.map} +1 -1
- package/dist/family.d.mts +5 -5
- package/dist/family.mjs +3 -3
- package/dist/family__control.d.mts +3 -3
- package/dist/family__control.mjs +2 -2
- package/dist/family__diff.d.mts +1 -1
- package/dist/family__migration.d.mts +1 -1
- package/dist/family__migration.mjs +1 -1
- package/dist/{migration-D6NJlA6-.d.mts → migration-C5HGygKM.d.mts} +3 -3
- package/dist/{migration-D6NJlA6-.d.mts.map → migration-C5HGygKM.d.mts.map} +1 -1
- package/dist/{migration-CK4oePIo.mjs → migration-Dy_4Wmiy.mjs} +3 -3
- package/dist/{migration-CK4oePIo.mjs.map → migration-Dy_4Wmiy.mjs.map} +1 -1
- package/dist/orm-client.d.mts +120 -120
- package/dist/orm-client.d.mts.map +1 -1
- package/dist/orm-client.mjs +1268 -1268
- package/dist/orm-client.mjs.map +1 -1
- package/dist/{types-CvuS68sy-ReNXbgpE.d.mts → types-CzxOObSY-CNWyrf87.d.mts} +12 -8
- package/dist/{types-CvuS68sy-ReNXbgpE.d.mts.map → types-CzxOObSY-CNWyrf87.d.mts.map} +1 -1
- package/package.json +18 -18
- package/dist/control-DCt08EpW.d.mts.map +0 -1
- package/dist/control-DxvOSmwF.mjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { u as namingOf } from "./naming-DkG_OegU-DpENNdZX.mjs";
|
|
2
2
|
import { t as sqlContractCanonicalizationHooks } from "./canonicalization-hooks-dmXkOR4a.mjs";
|
|
3
3
|
import { a as renderSqlTypeName, l as sqlDataTypeOfCodec, t as dataTypeParams, u as unquotedSqlBaseName } from "./data-type-CAPmGXg9.mjs";
|
|
4
|
-
import { c as StorageTable
|
|
4
|
+
import { c as StorageTable } from "./storage-table-CyL4CNMi-ByGZXmzd.mjs";
|
|
5
5
|
import { a as sqlExpressionAuthoringEntry, o as sqlExpressionDataType, t as SQL_EXPRESSION_DATA_TYPE_ID } from "./sql-expression-D7GsLgDM.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { u as isStorageTypeInstance } from "./types-DDHgx581.mjs";
|
|
7
7
|
import { m as valueObjectStorageTypeMissingMessage } from "./validators-Xx-TKcx3.mjs";
|
|
8
8
|
import { c as SqlSchemaIR, n as RelationalSchemaNodeKind, u as SqlTableIR } from "./types-Cz_bcUWN-CC3lFQOi.mjs";
|
|
9
9
|
import { a as describeUnsupportedSqlAttribute, b as sqlAttributeSpecs } from "./psl-field-resolution-DUCrDnuq-DSxPrd-4.mjs";
|
|
@@ -21,8 +21,8 @@ import { assembleDataTypes, canonicalFormOf } from "@prisma/orm-framework/compon
|
|
|
21
21
|
import { blindCast } from "@prisma/orm-framework/utils/casts";
|
|
22
22
|
import { ifDefined } from "@prisma/orm-framework/utils/defined";
|
|
23
23
|
import { InternalError } from "@prisma/orm-framework/utils/internal-error";
|
|
24
|
-
import { effectiveControlPolicy } from "@prisma/orm-framework/contract/types";
|
|
25
|
-
import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions, extractCodecLookup, issueOutcome, sameMarkerHashes } from "@prisma/orm-framework/components/control";
|
|
24
|
+
import { asNamespaceId, effectiveControlPolicy } from "@prisma/orm-framework/contract/types";
|
|
25
|
+
import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions, describeMigrationStatement, extractCodecLookup, issueOutcome, modelDisplayName, sameMarkerHashes } from "@prisma/orm-framework/components/control";
|
|
26
26
|
import { notOk, ok } from "@prisma/orm-framework/utils/result";
|
|
27
27
|
import { assertDescriptorSelfConsistency } from "@prisma/orm-toolchain/migration-tools/spaces";
|
|
28
28
|
//#region ../../../2-sql/9-family/dist/control.mjs
|
|
@@ -76,6 +76,38 @@ function assertContractMatchesStack(contract, stack) {
|
|
|
76
76
|
const problems = [...dataTypeProblems(contract.storage, stack.codecLookup), ...valueObjectProblems(contract, stack.authoringContributions)];
|
|
77
77
|
if (problems.length > 0) throw new ContractValidationError(problems.join("; "), "storage");
|
|
78
78
|
}
|
|
79
|
+
function isStorageDetails(details) {
|
|
80
|
+
if (typeof details !== "object" || details === null) return false;
|
|
81
|
+
const [objectType, name, schema, table] = [
|
|
82
|
+
"objectType",
|
|
83
|
+
"name",
|
|
84
|
+
"schema",
|
|
85
|
+
"table"
|
|
86
|
+
].map((key) => Reflect.get(details, key));
|
|
87
|
+
return typeof objectType === "string" && typeof name === "string" && (schema === void 0 || typeof schema === "string") && (table === void 0 || typeof table === "string");
|
|
88
|
+
}
|
|
89
|
+
function storageDetailsOf(operation) {
|
|
90
|
+
const target = Reflect.get(operation, "target");
|
|
91
|
+
if (typeof target !== "object" || target === null) return void 0;
|
|
92
|
+
const details = Reflect.get(target, "details");
|
|
93
|
+
return isStorageDetails(details) ? details : void 0;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The name the database knows an operation's object by, read from the operation's target
|
|
97
|
+
* details: `schema.table.column` for a column, `schema.name` for anything else, without the
|
|
98
|
+
* schema in the unbound namespace. An operation without details is named by its id.
|
|
99
|
+
*/
|
|
100
|
+
function storageNameOfOperation(operation) {
|
|
101
|
+
const details = storageDetailsOf(operation);
|
|
102
|
+
if (details === void 0) return operation.id;
|
|
103
|
+
const schema = details.schema === void 0 || details.schema === UNBOUND_NAMESPACE_ID ? [] : [details.schema];
|
|
104
|
+
const table = details.objectType === "column" && details.table !== void 0 ? [details.table] : [];
|
|
105
|
+
return [
|
|
106
|
+
...schema,
|
|
107
|
+
...table,
|
|
108
|
+
details.name
|
|
109
|
+
].join(".");
|
|
110
|
+
}
|
|
79
111
|
function isDdlStatement(sqlStatement) {
|
|
80
112
|
const trimmed = sqlStatement.trim().toLowerCase();
|
|
81
113
|
return trimmed.startsWith("create ") || trimmed.startsWith("alter ") || trimmed.startsWith("drop ");
|
|
@@ -535,6 +567,9 @@ function createSqlFamilyInstance(stack) {
|
|
|
535
567
|
toOperationPreview(operations) {
|
|
536
568
|
return sqlOperationsToPreview(operations);
|
|
537
569
|
},
|
|
570
|
+
storageNameOf(operation) {
|
|
571
|
+
return storageNameOfOperation(operation);
|
|
572
|
+
},
|
|
538
573
|
toSchemaView(schema) {
|
|
539
574
|
const root = blindCast(schema);
|
|
540
575
|
const namespaceEntries = root.namespaces !== void 0 ? Object.entries(root.namespaces).map(([namespaceKey, namespace]) => [namespaceKey, namespace.tables]) : [[void 0, root.tables ?? {}]];
|
|
@@ -652,118 +687,6 @@ var SqlFamilyDescriptor = class {
|
|
|
652
687
|
return createSqlFamilyInstance(stack);
|
|
653
688
|
}
|
|
654
689
|
};
|
|
655
|
-
const TABLE_RENAME_UNMATCHED_CODE = "MIGRATION.TABLE_RENAME_UNMATCHED";
|
|
656
|
-
function tableLabel(namespaceId, tableName) {
|
|
657
|
-
return namespaceId === void 0 || namespaceId === UNBOUND_NAMESPACE_ID ? tableName : `${namespaceId}.${tableName}`;
|
|
658
|
-
}
|
|
659
|
-
function namespacesDeclaring(contract, tableName) {
|
|
660
|
-
return Object.values(contract.storage.namespaces).filter((namespace) => Object.hasOwn(namespace.entries.table ?? {}, tableName)).map((namespace) => namespace.id);
|
|
661
|
-
}
|
|
662
|
-
function declares(contract, namespaceId, tableName) {
|
|
663
|
-
return Object.hasOwn(contract.storage.namespaces[namespaceId]?.entries.table ?? {}, tableName);
|
|
664
|
-
}
|
|
665
|
-
function unmatched(rename, reason) {
|
|
666
|
-
return sqlFamilyError(TABLE_RENAME_UNMATCHED_CODE, `renameTable "${tableLabel(rename.namespaceId, rename.from)}" to "${rename.to}" does not match the migration's contracts: ${reason}.`, {
|
|
667
|
-
why: "renameTable must name a table of the migration's start contract and a new name that the end contract has and the start contract does not. Make the rename its own schema change, so the migration's start contract is the schema before the rename and its end contract the schema after it, and check the spelling and the namespace.",
|
|
668
|
-
meta: {
|
|
669
|
-
from: rename.from,
|
|
670
|
-
to: rename.to
|
|
671
|
-
}
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
function resolveTableRename(rename, startContract, endContract) {
|
|
675
|
-
const namespaceIds = rename.namespaceId === void 0 ? namespacesDeclaring(startContract, rename.from) : declares(startContract, rename.namespaceId, rename.from) ? [rename.namespaceId] : [];
|
|
676
|
-
const [namespaceId, ...others] = namespaceIds;
|
|
677
|
-
if (namespaceId === void 0) return notOk(unmatched(rename, `table "${tableLabel(rename.namespaceId, rename.from)}" does not exist in the start contract`));
|
|
678
|
-
if (others.length > 0) return notOk(unmatched(rename, `table "${rename.from}" is declared in more than one namespace (${namespaceIds.join(", ")}); name its namespace`));
|
|
679
|
-
if (declares(startContract, namespaceId, rename.to)) return notOk(unmatched(rename, `table "${tableLabel(namespaceId, rename.to)}" already exists in the start contract`));
|
|
680
|
-
if (!declares(endContract, namespaceId, rename.to)) return notOk(unmatched(rename, `table "${tableLabel(namespaceId, rename.to)}" does not exist in the end contract`));
|
|
681
|
-
return ok({
|
|
682
|
-
namespaceId,
|
|
683
|
-
from: rename.from,
|
|
684
|
-
to: rename.to
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
function renamedReference(reference, rename) {
|
|
688
|
-
if (!(reference.spaceId === void 0) || reference.namespaceId !== rename.namespaceId || reference.tableName !== rename.from) return reference;
|
|
689
|
-
return {
|
|
690
|
-
...reference,
|
|
691
|
-
tableName: rename.to
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
function renameForeignKeys(table, rename) {
|
|
695
|
-
if (!table.foreignKeys.some((fk) => renamedReference(fk.source, rename) !== fk.source || renamedReference(fk.target, rename) !== fk.target)) return table;
|
|
696
|
-
return new StorageTable({
|
|
697
|
-
...table,
|
|
698
|
-
foreignKeys: table.foreignKeys.map((fk) => new ForeignKey({
|
|
699
|
-
...fk,
|
|
700
|
-
source: renamedReference(fk.source, rename),
|
|
701
|
-
target: renamedReference(fk.target, rename)
|
|
702
|
-
}))
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* The same namespace instance with only `entries` replaced: every own
|
|
707
|
-
* property, including the non-enumerable `kind`, is carried over onto the
|
|
708
|
-
* same prototype, so the target's namespace class (its `qualifyTable`,
|
|
709
|
-
* `ddlSchemaName`, entity getters) keeps working on the copy. This is the
|
|
710
|
-
* one place a frozen contract node is rebuilt without its constructor: the
|
|
711
|
-
* constructors re-hydrate `entries` from raw input, which the already
|
|
712
|
-
* hydrated entities here do not need.
|
|
713
|
-
*/
|
|
714
|
-
function withEntries(namespace, entries) {
|
|
715
|
-
const copy = Object.create(Object.getPrototypeOf(namespace), {
|
|
716
|
-
...Object.getOwnPropertyDescriptors(namespace),
|
|
717
|
-
entries: {
|
|
718
|
-
value: Object.freeze(entries),
|
|
719
|
-
enumerable: true,
|
|
720
|
-
writable: false,
|
|
721
|
-
configurable: false
|
|
722
|
-
}
|
|
723
|
-
});
|
|
724
|
-
return Object.freeze(copy);
|
|
725
|
-
}
|
|
726
|
-
function renameTableInNamespace(namespace, rename, renameTableReferences) {
|
|
727
|
-
const ownsTable = rename.namespaceId === namespace.id;
|
|
728
|
-
const tables = Object.entries(namespace.entries.table ?? {}).map(([name, table]) => [ownsTable && name === rename.from ? rename.to : name, renameForeignKeys(table, rename)]);
|
|
729
|
-
if (tables.every(([name, table]) => namespace.entries.table?.[name] === table)) return namespace;
|
|
730
|
-
if (!isMaterializedSqlNamespace(namespace)) throw new InternalError(`applyTableRename: namespace "${namespace.id}" is not a materialized SQL namespace`);
|
|
731
|
-
const renamedTables = {
|
|
732
|
-
...namespace.entries,
|
|
733
|
-
table: Object.fromEntries(tables)
|
|
734
|
-
};
|
|
735
|
-
return withEntries(namespace, renameTableReferences === void 0 || !ownsTable ? renamedTables : renameTableReferences(renamedTables, rename));
|
|
736
|
-
}
|
|
737
|
-
function renameTableInContract(contract, rename, renameTableReferences) {
|
|
738
|
-
const namespaces = Object.fromEntries(Object.entries(contract.storage.namespaces).map(([id, namespace]) => [id, renameTableInNamespace(namespace, rename, renameTableReferences)]));
|
|
739
|
-
const materialized = {};
|
|
740
|
-
for (const [id, namespace] of Object.entries(namespaces)) {
|
|
741
|
-
if (!isMaterializedSqlNamespace(namespace)) throw new InternalError(`applyTableRename: namespace "${id}" is not a materialized SQL namespace`);
|
|
742
|
-
materialized[id] = namespace;
|
|
743
|
-
}
|
|
744
|
-
return {
|
|
745
|
-
...contract,
|
|
746
|
-
storage: new SqlStorage({
|
|
747
|
-
storageHash: contract.storage.storageHash,
|
|
748
|
-
...contract.storage.types === void 0 ? {} : { types: contract.storage.types },
|
|
749
|
-
namespaces: materialized
|
|
750
|
-
})
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
/**
|
|
754
|
-
* Resolves the table a migration renames against its start and end contracts and returns the start contract with that table under its new name, so a diff against the end contract sees the table under one name. The table must exist in the start contract (in exactly one namespace when the namespace is not given), and the new name must exist in the end contract and not in the start contract; otherwise the rename is refused with `MIGRATION.TABLE_RENAME_UNMATCHED`.
|
|
755
|
-
*
|
|
756
|
-
* Foreign keys that name the renamed table on either side are retargeted. Index, unique, check and primary-key names are carried unchanged. Target entity kinds that name the table are renamed by the target's `renameTableReferences`.
|
|
757
|
-
*/
|
|
758
|
-
function applyTableRename(input) {
|
|
759
|
-
if (input.startContract === null) return notOk(unmatched(input.rename, "the migration has no start contract"));
|
|
760
|
-
const resolved = resolveTableRename(input.rename, input.startContract, input.endContract);
|
|
761
|
-
if (!resolved.ok) return resolved;
|
|
762
|
-
return ok({
|
|
763
|
-
contract: renameTableInContract(input.startContract, resolved.value, input.renameTableReferences),
|
|
764
|
-
rename: resolved.value
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
690
|
function convertColumn(name, column, storageTypes, types, renderDefault, resolveDefault) {
|
|
768
691
|
const resolved = resolveColumnTypeMetadata(column, storageTypes);
|
|
769
692
|
const dataType = sqlDataTypeOfCodec(resolved.codecId, types);
|
|
@@ -1205,8 +1128,15 @@ function subjectKey(subject) {
|
|
|
1205
1128
|
return `${subject.namespaceId}\u0000${subject.entityKind ?? ""}\u0000${subject.entityName ?? ""}\u0000${subject.rlsPolicy ?? ""}`;
|
|
1206
1129
|
}
|
|
1207
1130
|
function planFieldEventOperations(options) {
|
|
1131
|
+
return planFieldEventCalls(options).map(({ call }) => call);
|
|
1132
|
+
}
|
|
1133
|
+
/** The calls the codec hooks return, each with the field event's column. */
|
|
1134
|
+
function planFieldEventCalls(options) {
|
|
1208
1135
|
const priorContract = options.priorContract;
|
|
1209
1136
|
const newContract = options.newContract;
|
|
1137
|
+
const renamedTo = new Map(options.tableRenames.map((rename) => [renameKey(rename.namespaceId, rename.from), rename.to]));
|
|
1138
|
+
const newNameOf = (namespaceId, tableName) => renamedTo.get(renameKey(namespaceId, tableName));
|
|
1139
|
+
const columnRenamedTo = new Map(options.columnRenames.map((rename) => [renameKey(rename.namespaceId, rename.table, rename.from), rename.to]));
|
|
1210
1140
|
const added = [];
|
|
1211
1141
|
const dropped = [];
|
|
1212
1142
|
const altered = [];
|
|
@@ -1214,7 +1144,7 @@ function planFieldEventOperations(options) {
|
|
|
1214
1144
|
for (const namespaceId of namespaceIds) {
|
|
1215
1145
|
const priorNs = priorContract?.storage.namespaces[namespaceId];
|
|
1216
1146
|
const newNs = newContract.storage.namespaces[namespaceId];
|
|
1217
|
-
const priorTables = priorNs?.entries.table;
|
|
1147
|
+
const priorTables = underNewNames(priorNs?.entries.table, (tableName) => newNameOf(namespaceId, tableName));
|
|
1218
1148
|
const newTables = newNs?.entries.table;
|
|
1219
1149
|
const tableNames = unionSorted(priorTables ? Object.keys(priorTables) : [], newTables ? Object.keys(newTables) : []);
|
|
1220
1150
|
for (const tableName of tableNames) {
|
|
@@ -1222,9 +1152,10 @@ function planFieldEventOperations(options) {
|
|
|
1222
1152
|
const newTableRaw = newTables?.[tableName];
|
|
1223
1153
|
const priorTable = StorageTable.is(priorTableRaw) ? priorTableRaw : void 0;
|
|
1224
1154
|
const newTable = StorageTable.is(newTableRaw) ? newTableRaw : void 0;
|
|
1225
|
-
const
|
|
1155
|
+
const priorColumns = underNewNames(priorTable?.columns, (columnName) => columnRenamedTo.get(renameKey(namespaceId, tableName, columnName)));
|
|
1156
|
+
const fieldNames = unionSorted(priorColumns ? Object.keys(priorColumns) : [], newTable ? Object.keys(newTable.columns) : []);
|
|
1226
1157
|
for (const fieldName of fieldNames) {
|
|
1227
|
-
const priorField =
|
|
1158
|
+
const priorField = priorColumns?.[fieldName];
|
|
1228
1159
|
const newField = newTable?.columns[fieldName];
|
|
1229
1160
|
const entry = {
|
|
1230
1161
|
namespaceId,
|
|
@@ -1249,6 +1180,13 @@ function planFieldEventOperations(options) {
|
|
|
1249
1180
|
appendCalls("altered", altered, options.codecHooks, calls, (e) => e.newField?.codecId);
|
|
1250
1181
|
return calls;
|
|
1251
1182
|
}
|
|
1183
|
+
function renameKey(...parts) {
|
|
1184
|
+
return JSON.stringify(parts);
|
|
1185
|
+
}
|
|
1186
|
+
function underNewNames(tables, newNameOf) {
|
|
1187
|
+
if (tables === void 0) return void 0;
|
|
1188
|
+
return Object.fromEntries(Object.entries(tables).map(([tableName, table]) => [newNameOf(tableName) ?? tableName, table]));
|
|
1189
|
+
}
|
|
1252
1190
|
function appendCalls(event, entries, codecHooks, calls, pickCodecId) {
|
|
1253
1191
|
for (const entry of entries) {
|
|
1254
1192
|
const codecId = pickCodecId(entry);
|
|
@@ -1257,7 +1195,12 @@ function appendCalls(event, entries, codecHooks, calls, pickCodecId) {
|
|
|
1257
1195
|
if (!hook?.onFieldEvent) continue;
|
|
1258
1196
|
const ctx = buildContext(event, entry);
|
|
1259
1197
|
const emitted = hook.onFieldEvent(event, ctx);
|
|
1260
|
-
for (const call of emitted) calls.push(
|
|
1198
|
+
for (const call of emitted) calls.push({
|
|
1199
|
+
call,
|
|
1200
|
+
namespaceId: entry.namespaceId,
|
|
1201
|
+
tableName: entry.tableName,
|
|
1202
|
+
columnName: entry.fieldName
|
|
1203
|
+
});
|
|
1261
1204
|
}
|
|
1262
1205
|
}
|
|
1263
1206
|
/**
|
|
@@ -1326,6 +1269,560 @@ function unionSorted(a, b) {
|
|
|
1326
1269
|
for (const name of b) set.add(name);
|
|
1327
1270
|
return [...set].sort((x, y) => x < y ? -1 : x > y ? 1 : 0);
|
|
1328
1271
|
}
|
|
1272
|
+
const COLUMN_RENAME_UNMATCHED_CODE = "MIGRATION.COLUMN_RENAME_UNMATCHED";
|
|
1273
|
+
function tableLabel$1(namespaceId, table) {
|
|
1274
|
+
return namespaceId === void 0 || namespaceId === UNBOUND_NAMESPACE_ID ? table : `${namespaceId}.${table}`;
|
|
1275
|
+
}
|
|
1276
|
+
function columnLabel(namespaceId, table, column) {
|
|
1277
|
+
return `"${tableLabel$1(namespaceId, table)}"."${column}"`;
|
|
1278
|
+
}
|
|
1279
|
+
function declaresColumn(contract, namespaceId, table, column) {
|
|
1280
|
+
const columns = contract.storage.namespaces[namespaceId]?.entries.table?.[table]?.columns;
|
|
1281
|
+
return columns !== void 0 && Object.hasOwn(columns, column);
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Refuses a `renameColumn` call that does not match the migration's contracts, with
|
|
1285
|
+
* `MIGRATION.COLUMN_RENAME_UNMATCHED`.
|
|
1286
|
+
*/
|
|
1287
|
+
function unmatchedColumnRename(rename, reason) {
|
|
1288
|
+
return sqlFamilyError(COLUMN_RENAME_UNMATCHED_CODE, `renameColumn ${columnLabel(rename.namespaceId, rename.table, rename.from)} to "${rename.to}" does not match the migration's contracts: ${reason}.`, {
|
|
1289
|
+
why: "renameColumn must name a table and a column as the migration's earlier rename operations leave them, and a new column name that the end contract has on that table and that the table does not have yet. Order the rename calls in the sequence the renames happen, and check the names of the table, the column and the namespace.",
|
|
1290
|
+
meta: {
|
|
1291
|
+
table: rename.table,
|
|
1292
|
+
from: rename.from,
|
|
1293
|
+
to: rename.to
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Checks a column rename: the table must exist in `previous` (in exactly one namespace when the
|
|
1299
|
+
* namespace is not given) with the old column and without the new one, and the end contract must
|
|
1300
|
+
* have the new column on that table.
|
|
1301
|
+
*/
|
|
1302
|
+
function checkColumnRename(previous, endContract, rename) {
|
|
1303
|
+
const namespaceIds = rename.namespaceId === void 0 ? previous.namespacesWithTable(rename.table) : previous.hasTable(rename.namespaceId, rename.table) ? [rename.namespaceId] : [];
|
|
1304
|
+
const [namespaceId, ...others] = namespaceIds;
|
|
1305
|
+
if (namespaceId === void 0) return notOk({ kind: "tableMissing" });
|
|
1306
|
+
if (others.length > 0) return notOk({
|
|
1307
|
+
kind: "tableInManyNamespaces",
|
|
1308
|
+
namespaceIds
|
|
1309
|
+
});
|
|
1310
|
+
if (!previous.hasColumn(namespaceId, rename.table, rename.from)) return notOk({
|
|
1311
|
+
kind: "columnMissing",
|
|
1312
|
+
namespaceId
|
|
1313
|
+
});
|
|
1314
|
+
const [taken] = previous.columnsNamed(namespaceId, rename.table, rename.to).filter((column) => column !== rename.from);
|
|
1315
|
+
if (taken !== void 0) return notOk({
|
|
1316
|
+
kind: "nameTaken",
|
|
1317
|
+
namespaceId,
|
|
1318
|
+
taken
|
|
1319
|
+
});
|
|
1320
|
+
if (!declaresColumn(endContract, namespaceId, rename.table, rename.to)) return notOk({
|
|
1321
|
+
kind: "notInEndContract",
|
|
1322
|
+
namespaceId
|
|
1323
|
+
});
|
|
1324
|
+
return ok({
|
|
1325
|
+
namespaceId,
|
|
1326
|
+
table: rename.table,
|
|
1327
|
+
from: rename.from,
|
|
1328
|
+
to: rename.to
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
/** The reason `MIGRATION.COLUMN_RENAME_UNMATCHED` gives for a mismatch. */
|
|
1332
|
+
function columnRenameMismatchReason(rename, mismatch) {
|
|
1333
|
+
switch (mismatch.kind) {
|
|
1334
|
+
case "tableMissing": return `table "${tableLabel$1(rename.namespaceId, rename.table)}" does not exist at this point of the migration`;
|
|
1335
|
+
case "tableInManyNamespaces": return `table "${rename.table}" is declared in more than one namespace (${mismatch.namespaceIds.join(", ")}); name its namespace`;
|
|
1336
|
+
case "columnMissing": return `column ${columnLabel(mismatch.namespaceId, rename.table, rename.from)} does not exist at this point of the migration`;
|
|
1337
|
+
case "nameTaken": return `column ${columnLabel(mismatch.namespaceId, rename.table, rename.to)} already exists at this point of the migration${mismatch.taken === rename.to ? "" : ` as "${mismatch.taken}"`}`;
|
|
1338
|
+
case "notInEndContract": return `column ${columnLabel(mismatch.namespaceId, rename.table, rename.to)} does not exist in the end contract`;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Resolves a column a migration renames with {@link checkColumnRename}, refusing a mismatch with
|
|
1343
|
+
* `MIGRATION.COLUMN_RENAME_UNMATCHED`.
|
|
1344
|
+
*/
|
|
1345
|
+
function resolveColumnRenameAgainst(previous, endContract, rename) {
|
|
1346
|
+
const checked = checkColumnRename(previous, endContract, rename);
|
|
1347
|
+
if (!checked.ok) return notOk(unmatchedColumnRename(rename, columnRenameMismatchReason(rename, checked.failure)));
|
|
1348
|
+
return ok(checked.value);
|
|
1349
|
+
}
|
|
1350
|
+
const TABLE_RENAME_UNMATCHED_CODE = "MIGRATION.TABLE_RENAME_UNMATCHED";
|
|
1351
|
+
function tableLabel(namespaceId, tableName) {
|
|
1352
|
+
return namespaceId === void 0 || namespaceId === UNBOUND_NAMESPACE_ID ? tableName : `${namespaceId}.${tableName}`;
|
|
1353
|
+
}
|
|
1354
|
+
function declares(contract, namespaceId, tableName) {
|
|
1355
|
+
return Object.hasOwn(contract.storage.namespaces[namespaceId]?.entries.table ?? {}, tableName);
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Refuses a `renameTable` call that does not match the migration's contracts, with
|
|
1359
|
+
* `MIGRATION.TABLE_RENAME_UNMATCHED`.
|
|
1360
|
+
*/
|
|
1361
|
+
function unmatchedTableRename(rename, reason) {
|
|
1362
|
+
return sqlFamilyError(TABLE_RENAME_UNMATCHED_CODE, `renameTable "${tableLabel(rename.namespaceId, rename.from)}" to "${rename.to}" does not match the migration's contracts: ${reason}.`, {
|
|
1363
|
+
why: "renameTable must name a table as the migration's earlier rename operations leave it, and a new name that the end contract has and that no earlier rename operation has already produced. Order the renameTable calls in the sequence the renames happen, make the rename its own schema change, and check the spelling, the table and the namespace.",
|
|
1364
|
+
meta: {
|
|
1365
|
+
from: rename.from,
|
|
1366
|
+
to: rename.to
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Checks a table rename: the table must exist in `previous` (in exactly one namespace when the
|
|
1372
|
+
* namespace is not given), and the new name must exist in the end contract and not in `previous`.
|
|
1373
|
+
*/
|
|
1374
|
+
function checkTableRename(previous, endContract, rename) {
|
|
1375
|
+
const namespaceIds = rename.namespaceId === void 0 ? previous.namespacesWithTable(rename.from) : previous.hasTable(rename.namespaceId, rename.from) ? [rename.namespaceId] : [];
|
|
1376
|
+
const [namespaceId, ...others] = namespaceIds;
|
|
1377
|
+
if (namespaceId === void 0) return notOk({ kind: "tableMissing" });
|
|
1378
|
+
if (others.length > 0) return notOk({
|
|
1379
|
+
kind: "tableInManyNamespaces",
|
|
1380
|
+
namespaceIds
|
|
1381
|
+
});
|
|
1382
|
+
const [taken] = previous.tablesNamed(namespaceId, rename.to).filter((table) => table !== rename.from);
|
|
1383
|
+
if (taken !== void 0) return notOk({
|
|
1384
|
+
kind: "nameTaken",
|
|
1385
|
+
namespaceId,
|
|
1386
|
+
taken
|
|
1387
|
+
});
|
|
1388
|
+
if (!declares(endContract, namespaceId, rename.to)) return notOk({
|
|
1389
|
+
kind: "notInEndContract",
|
|
1390
|
+
namespaceId
|
|
1391
|
+
});
|
|
1392
|
+
return ok({
|
|
1393
|
+
namespaceId,
|
|
1394
|
+
from: rename.from,
|
|
1395
|
+
to: rename.to
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
/** The reason `MIGRATION.TABLE_RENAME_UNMATCHED` gives for a mismatch. */
|
|
1399
|
+
function tableRenameMismatchReason(rename, mismatch) {
|
|
1400
|
+
switch (mismatch.kind) {
|
|
1401
|
+
case "tableMissing": return `table "${tableLabel(rename.namespaceId, rename.from)}" does not exist at this point of the migration`;
|
|
1402
|
+
case "tableInManyNamespaces": return `table "${rename.from}" is declared in more than one namespace (${mismatch.namespaceIds.join(", ")}); name its namespace`;
|
|
1403
|
+
case "nameTaken": return `table "${tableLabel(mismatch.namespaceId, rename.to)}" already exists at this point of the migration${mismatch.taken === rename.to ? "" : ` as "${mismatch.taken}"`}`;
|
|
1404
|
+
case "notInEndContract": return `table "${tableLabel(mismatch.namespaceId, rename.to)}" does not exist in the end contract`;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Resolves a table a migration renames with {@link checkTableRename}, refusing a mismatch with
|
|
1409
|
+
* `MIGRATION.TABLE_RENAME_UNMATCHED`.
|
|
1410
|
+
*/
|
|
1411
|
+
function resolveTableRenameAgainst(previous, endContract, rename) {
|
|
1412
|
+
const checked = checkTableRename(previous, endContract, rename);
|
|
1413
|
+
if (!checked.ok) return notOk(unmatchedTableRename(rename, tableRenameMismatchReason(rename, checked.failure)));
|
|
1414
|
+
return ok(checked.value);
|
|
1415
|
+
}
|
|
1416
|
+
/** Whether a model's storage names a table, as a model a SQL contract stores in a table does. */
|
|
1417
|
+
function isSqlModelStorage(storage) {
|
|
1418
|
+
return typeof storage["table"] === "string" && typeof storage["namespaceId"] === "string" && typeof storage["fields"] === "object" && storage["fields"] !== null;
|
|
1419
|
+
}
|
|
1420
|
+
/** The storage of a model the contract stores in a table, or `undefined` for any other model. */
|
|
1421
|
+
function sqlModelStorage(contract, coordinate) {
|
|
1422
|
+
const storage = contract.domain.namespaces[coordinate.namespaceId]?.models[coordinate.model]?.storage;
|
|
1423
|
+
return storage !== void 0 && isSqlModelStorage(storage) ? storage : void 0;
|
|
1424
|
+
}
|
|
1425
|
+
function modelTable(contract, coordinate) {
|
|
1426
|
+
const storage = sqlModelStorage(contract, coordinate);
|
|
1427
|
+
return storage === void 0 ? void 0 : {
|
|
1428
|
+
namespaceId: storage.namespaceId,
|
|
1429
|
+
table: storage.table
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* The storage effect of a model rename: the origin model's table compared with the destination
|
|
1434
|
+
* model's. Equal tables mean the rename needs no storage change.
|
|
1435
|
+
*/
|
|
1436
|
+
function modelRenameStorageEffect(statement, fromContract, contract) {
|
|
1437
|
+
const from = modelTable(fromContract, statement.from);
|
|
1438
|
+
if (from === void 0) return notOk({
|
|
1439
|
+
kind: "noTable",
|
|
1440
|
+
model: statement.from
|
|
1441
|
+
});
|
|
1442
|
+
const to = modelTable(contract, statement.to);
|
|
1443
|
+
if (to === void 0) return notOk({
|
|
1444
|
+
kind: "noTable",
|
|
1445
|
+
model: statement.to
|
|
1446
|
+
});
|
|
1447
|
+
if (from.namespaceId !== to.namespaceId) return ok({
|
|
1448
|
+
kind: "moveNamespace",
|
|
1449
|
+
from,
|
|
1450
|
+
to
|
|
1451
|
+
});
|
|
1452
|
+
if (from.table === to.table) return ok({ kind: "unchanged" });
|
|
1453
|
+
return ok({
|
|
1454
|
+
kind: "renameTable",
|
|
1455
|
+
rename: {
|
|
1456
|
+
namespaceId: from.namespaceId,
|
|
1457
|
+
from: from.table,
|
|
1458
|
+
to: to.table
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
function fieldColumn(contract, coordinate) {
|
|
1463
|
+
const fields = sqlModelStorage(contract, coordinate)?.fields;
|
|
1464
|
+
return fields !== void 0 && Object.hasOwn(fields, coordinate.field) ? fields[coordinate.field]?.column : void 0;
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* The storage effect of a field rename: the origin field's column compared with the destination
|
|
1468
|
+
* field's. A relation field has no column on either side, and equal columns need no change.
|
|
1469
|
+
*/
|
|
1470
|
+
function fieldRenameStorageEffect(statement, fromContract, contract) {
|
|
1471
|
+
const table = modelTable(fromContract, statement.from);
|
|
1472
|
+
if (table === void 0) return notOk({
|
|
1473
|
+
kind: "noTable",
|
|
1474
|
+
model: statement.from
|
|
1475
|
+
});
|
|
1476
|
+
const from = fieldColumn(fromContract, statement.from);
|
|
1477
|
+
const to = fieldColumn(contract, statement.to);
|
|
1478
|
+
if (from === void 0 && to === void 0) return ok({ kind: "unchanged" });
|
|
1479
|
+
if (from === void 0 || to === void 0) return notOk({ kind: "columnOnOneSide" });
|
|
1480
|
+
if (from === to) return ok({ kind: "unchanged" });
|
|
1481
|
+
return ok({
|
|
1482
|
+
kind: "renameColumn",
|
|
1483
|
+
table,
|
|
1484
|
+
from,
|
|
1485
|
+
to
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
function operationsOf(call) {
|
|
1489
|
+
return [call, ...call.companions];
|
|
1490
|
+
}
|
|
1491
|
+
const DRIFTED = "so the database has drifted from that contract. Inspect it with prisma db schema, or leave out this statement.";
|
|
1492
|
+
const UNMATCHED = "A statement plans the rename a hand-written rename call makes, and that call does not match the contracts. Leave out this statement.";
|
|
1493
|
+
function statementRefused(statement, summary, why, location, refusedOperationClass) {
|
|
1494
|
+
return {
|
|
1495
|
+
kind: "statementRefused",
|
|
1496
|
+
summary,
|
|
1497
|
+
why,
|
|
1498
|
+
refusedStatement: statement,
|
|
1499
|
+
...refusedOperationClass === void 0 ? {} : { refusedOperationClass },
|
|
1500
|
+
...location === void 0 ? {} : { location: {
|
|
1501
|
+
namespaceId: location.namespaceId,
|
|
1502
|
+
entityKind: "table",
|
|
1503
|
+
entityName: location.table,
|
|
1504
|
+
...location.column === void 0 ? {} : { column: location.column }
|
|
1505
|
+
} }
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
function tableControlPolicy(contract, table) {
|
|
1509
|
+
const node = contract.storage.namespaces[table.namespaceId]?.entries.table?.[table.table];
|
|
1510
|
+
return controlPolicyForCall({
|
|
1511
|
+
namespaceId: table.namespaceId,
|
|
1512
|
+
entityKind: "table",
|
|
1513
|
+
entityName: table.table,
|
|
1514
|
+
...StorageTable.is(node) && node.control !== void 0 ? { explicitNodeControlPolicy: node.control } : {},
|
|
1515
|
+
createsNewObject: false
|
|
1516
|
+
}, contract.defaultControlPolicy);
|
|
1517
|
+
}
|
|
1518
|
+
function tableKey(table) {
|
|
1519
|
+
return JSON.stringify([table.namespaceId, table.table]);
|
|
1520
|
+
}
|
|
1521
|
+
function tableRenameRefused(statement, label, rename, mismatch) {
|
|
1522
|
+
switch (mismatch.kind) {
|
|
1523
|
+
case "tableMissing": return statementRefused(statement, `${label}: the schema being planned from has no table "${rename.from}"`, `The database has no table "${rename.from}", although the contract it was last updated to names it, ${DRIFTED}`, {
|
|
1524
|
+
namespaceId: rename.namespaceId,
|
|
1525
|
+
table: rename.from
|
|
1526
|
+
});
|
|
1527
|
+
case "nameTaken": return statementRefused(statement, `${label}: the schema being planned from already has a table "${rename.to}"${mismatch.taken === rename.to ? "" : `, as "${mismatch.taken}"`}`, `A rename cannot replace a table that already exists. Rename or drop table "${mismatch.taken}" first, or leave out this statement.`, {
|
|
1528
|
+
namespaceId: rename.namespaceId,
|
|
1529
|
+
table: rename.to
|
|
1530
|
+
});
|
|
1531
|
+
case "tableInManyNamespaces":
|
|
1532
|
+
case "notInEndContract": return statementRefused(statement, `${label}: ${tableRenameMismatchReason(rename, mismatch)}`, UNMATCHED, {
|
|
1533
|
+
namespaceId: rename.namespaceId,
|
|
1534
|
+
table: rename.from
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
function columnRenameRefused(statement, label, rename, mismatch) {
|
|
1539
|
+
const table = {
|
|
1540
|
+
namespaceId: rename.namespaceId,
|
|
1541
|
+
table: rename.table
|
|
1542
|
+
};
|
|
1543
|
+
switch (mismatch.kind) {
|
|
1544
|
+
case "tableMissing": return statementRefused(statement, `${label}: the schema being planned from has no table "${rename.table}"`, `The database has no table "${rename.table}", although the contract it was last updated to names it, ${DRIFTED}`, table);
|
|
1545
|
+
case "columnMissing": return statementRefused(statement, `${label}: the schema being planned from has no column "${rename.from}" on table "${rename.table}"`, `The database has no column "${rename.from}" on table "${rename.table}", although the contract it was last updated to names it, ${DRIFTED}`, {
|
|
1546
|
+
...table,
|
|
1547
|
+
column: rename.from
|
|
1548
|
+
});
|
|
1549
|
+
case "nameTaken": return statementRefused(statement, `${label}: the schema being planned from already has a column "${mismatch.taken}" on table "${rename.table}"`, `A rename cannot replace a column that already exists. Rename or drop column "${mismatch.taken}" of table "${rename.table}" first, or leave out this statement.`, {
|
|
1550
|
+
...table,
|
|
1551
|
+
column: rename.to
|
|
1552
|
+
});
|
|
1553
|
+
case "tableInManyNamespaces":
|
|
1554
|
+
case "notInEndContract": return statementRefused(statement, `${label}: ${columnRenameMismatchReason(rename, mismatch)}`, UNMATCHED, {
|
|
1555
|
+
...table,
|
|
1556
|
+
column: rename.from
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
var StatementPlanner = class {
|
|
1561
|
+
#fromContract;
|
|
1562
|
+
#contract;
|
|
1563
|
+
#policy;
|
|
1564
|
+
#target;
|
|
1565
|
+
/** The name each table renamed so far has in the working schema, keyed by its origin name. */
|
|
1566
|
+
#renamedTables = /* @__PURE__ */ new Map();
|
|
1567
|
+
calls = [];
|
|
1568
|
+
/** How many operations the calls planned so far lower to. */
|
|
1569
|
+
#operationCount = 0;
|
|
1570
|
+
tableRenames = [];
|
|
1571
|
+
columnRenames = [];
|
|
1572
|
+
constructor(input) {
|
|
1573
|
+
this.#fromContract = input.fromContract;
|
|
1574
|
+
this.#contract = input.contract;
|
|
1575
|
+
this.#policy = input.policy;
|
|
1576
|
+
this.#target = input.target;
|
|
1577
|
+
}
|
|
1578
|
+
/** Plans one statement; the result is the positions of the operations it accounts for. */
|
|
1579
|
+
plan(statement) {
|
|
1580
|
+
return statement.entity === "model" ? this.#planModel(statement) : this.#planField(statement);
|
|
1581
|
+
}
|
|
1582
|
+
#controlPolicyRefusal(statement, label, destinationTable) {
|
|
1583
|
+
const controlPolicy = tableControlPolicy(this.#contract, destinationTable);
|
|
1584
|
+
if (controlPolicy === "managed") return void 0;
|
|
1585
|
+
return statementRefused(statement, `${label}: the table's control policy is "${controlPolicy}"`, `Statements rename only tables, and columns of tables, whose control policy is "managed"; table "${destinationTable.table}" is "${controlPolicy}". Make the change in the database yourself and leave out this statement.`, destinationTable);
|
|
1586
|
+
}
|
|
1587
|
+
#emit(statement, label, location, call) {
|
|
1588
|
+
const refused = operationsOf(call).map((operation) => operation.operationClass).find((operationClass) => !this.#policy.allowedOperationClasses.includes(operationClass));
|
|
1589
|
+
if (refused !== void 0) return notOk(statementRefused(statement, `${label}: the plan does not allow "${refused}" operations`, `The rename produces a "${refused}" operation, and this command plans only ${this.#policy.allowedOperationClasses.map((c) => `"${c}"`).join(", ")} operations. Leave out this statement, or make the change with a command that allows "${refused}" operations, such as migration plan.`, location, refused));
|
|
1590
|
+
this.#target.apply(call);
|
|
1591
|
+
this.calls.push(call);
|
|
1592
|
+
const first = this.#operationCount;
|
|
1593
|
+
this.#operationCount += operationsOf(call).length;
|
|
1594
|
+
return ok(operationsOf(call).map((_, offset) => first + offset));
|
|
1595
|
+
}
|
|
1596
|
+
#planModel(statement) {
|
|
1597
|
+
const worked = modelRenameStorageEffect(statement, this.#fromContract, this.#contract);
|
|
1598
|
+
if (!worked.ok) return notOk(statementRefused(statement, `Model "${modelDisplayName(worked.failure.model)}" has no table in its contract`, `A model rename renames the model's table, and model "${modelDisplayName(worked.failure.model)}" has none in its contract. Leave out this statement.`, void 0));
|
|
1599
|
+
const effect = worked.value;
|
|
1600
|
+
if (effect.kind === "unchanged") return ok([]);
|
|
1601
|
+
if (effect.kind === "moveNamespace") return notOk(statementRefused(statement, `Moving a model to another namespace is not supported in this release: "${modelDisplayName(statement.from)}" to "${modelDisplayName(statement.to)}"`, `The model's table would move from namespace "${effect.from.namespaceId}" to namespace "${effect.to.namespaceId}". Leave out this statement and move the table yourself in a hand-written migration, or keep the model in namespace "${effect.from.namespaceId}".`, effect.from));
|
|
1602
|
+
const { rename } = effect;
|
|
1603
|
+
const destinationTable = {
|
|
1604
|
+
namespaceId: rename.namespaceId,
|
|
1605
|
+
table: rename.to
|
|
1606
|
+
};
|
|
1607
|
+
const label = `Cannot rename table "${rename.from}" to "${rename.to}"`;
|
|
1608
|
+
const policyRefusal = this.#controlPolicyRefusal(statement, label, destinationTable);
|
|
1609
|
+
if (policyRefusal !== void 0) return notOk(policyRefusal);
|
|
1610
|
+
const checked = checkTableRename(this.#target.tables(), this.#contract, rename);
|
|
1611
|
+
if (!checked.ok) return notOk(tableRenameRefused(statement, label, rename, checked.failure));
|
|
1612
|
+
const planned = this.#emit(statement, label, destinationTable, this.#target.renameTableCall(rename));
|
|
1613
|
+
if (planned.ok) {
|
|
1614
|
+
this.tableRenames.push(rename);
|
|
1615
|
+
this.#renamedTables.set(tableKey({
|
|
1616
|
+
namespaceId: rename.namespaceId,
|
|
1617
|
+
table: rename.from
|
|
1618
|
+
}), rename.to);
|
|
1619
|
+
}
|
|
1620
|
+
return planned;
|
|
1621
|
+
}
|
|
1622
|
+
#planField(statement) {
|
|
1623
|
+
const worked = fieldRenameStorageEffect(statement, this.#fromContract, this.#contract);
|
|
1624
|
+
if (!worked.ok && worked.failure.kind === "noTable") return notOk(statementRefused(statement, `Model "${modelDisplayName(worked.failure.model)}" has no table in its contract`, `A field rename renames the field's column, and model "${modelDisplayName(worked.failure.model)}" has no table in its contract. Leave out this statement.`, void 0));
|
|
1625
|
+
if (!worked.ok) return notOk(statementRefused(statement, `Field "${modelDisplayName(statement.from)}.${statement.from.field}" has a column on one side only of ${describeMigrationStatement(statement, this.#fromContract, this.#contract)}`, "A field rename renames a column or changes nothing in storage, and this field gains or loses its column. Leave out this statement and plan the change without it.", void 0));
|
|
1626
|
+
const effect = worked.value;
|
|
1627
|
+
if (effect.kind === "unchanged") return ok([]);
|
|
1628
|
+
const table = this.#renamedTables.get(tableKey(effect.table)) ?? effect.table.table;
|
|
1629
|
+
const destinationTable = modelTable(this.#contract, statement.to);
|
|
1630
|
+
if (destinationTable !== void 0 && (destinationTable.namespaceId !== effect.table.namespaceId || destinationTable.table !== table)) {
|
|
1631
|
+
const tableRename = {
|
|
1632
|
+
namespaceId: effect.table.namespaceId,
|
|
1633
|
+
from: table,
|
|
1634
|
+
to: destinationTable.table
|
|
1635
|
+
};
|
|
1636
|
+
const model = modelDisplayName(statement.to);
|
|
1637
|
+
const fieldText = `${model}.${statement.from.field}:${model}.${statement.to.field}`;
|
|
1638
|
+
return notOk(statementRefused(statement, `Cannot rename column "${table}"."${effect.from}": the model's table changes from "${table}" to "${destinationTable.table}", and no statement renames the table`, `The column would be renamed on a table the plan then drops and creates under the new name, and a migration that only changes the table name is planned the same way. With migration plan, rename the table by hand in its own migration first. Change the contract so that only the table name changes, and run prisma contract emit. Run prisma migration new --name <name> --from <hash of the migration the database is at>, add ${this.#target.renderTableRename(tableRename)} to the operations of its migration.ts, and run node on that migration.ts to write its ops.json. Then change the contract to its final form, emit it, and plan the field rename with --from that migration. With db update, change the contract so that only the table name changes, and run prisma contract emit. Rename the table in the database yourself with ${this.#target.tableRenameByHand(tableRename).join("; ")}, check that prisma db update --dry-run plans no drop of a table or column, and run prisma db update to store that contract. Then emit the final contract and run prisma db update --rename ${fieldText}.`, {
|
|
1639
|
+
namespaceId: effect.table.namespaceId,
|
|
1640
|
+
table,
|
|
1641
|
+
column: effect.from
|
|
1642
|
+
}));
|
|
1643
|
+
}
|
|
1644
|
+
const rename = {
|
|
1645
|
+
namespaceId: effect.table.namespaceId,
|
|
1646
|
+
table,
|
|
1647
|
+
from: effect.from,
|
|
1648
|
+
to: effect.to
|
|
1649
|
+
};
|
|
1650
|
+
const tableLocation = {
|
|
1651
|
+
namespaceId: rename.namespaceId,
|
|
1652
|
+
table
|
|
1653
|
+
};
|
|
1654
|
+
const label = `Cannot rename column "${table}"."${rename.from}" to "${rename.to}"`;
|
|
1655
|
+
const policyRefusal = this.#controlPolicyRefusal(statement, label, tableLocation);
|
|
1656
|
+
if (policyRefusal !== void 0) return notOk(policyRefusal);
|
|
1657
|
+
const checked = checkColumnRename(this.#target.tables(), this.#contract, rename);
|
|
1658
|
+
if (!checked.ok) return notOk(columnRenameRefused(statement, label, rename, checked.failure));
|
|
1659
|
+
const planned = this.#emit(statement, label, {
|
|
1660
|
+
...tableLocation,
|
|
1661
|
+
column: rename.from
|
|
1662
|
+
}, this.#target.renameColumnCall(rename));
|
|
1663
|
+
if (planned.ok) this.columnRenames.push(rename);
|
|
1664
|
+
return planned;
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1667
|
+
/**
|
|
1668
|
+
* Plans the statements in order against a target's working schema: each model rename becomes a
|
|
1669
|
+
* table rename and each field rename a column rename, computed against the schema earlier
|
|
1670
|
+
* statements left, then applied to it. The first statement that cannot be planned fails the whole
|
|
1671
|
+
* plan with a `statementRefused` conflict that carries the statement.
|
|
1672
|
+
*/
|
|
1673
|
+
function planStatements(input) {
|
|
1674
|
+
const { fromContract } = input;
|
|
1675
|
+
const [first] = input.statements;
|
|
1676
|
+
if (first === void 0) return ok({
|
|
1677
|
+
calls: [],
|
|
1678
|
+
tableRenames: [],
|
|
1679
|
+
columnRenames: [],
|
|
1680
|
+
appliedStatements: []
|
|
1681
|
+
});
|
|
1682
|
+
if (fromContract === null) return notOk(statementRefused(first, "Statements need an origin contract, and this plan has none", "A statement names models and fields of the origin contract, and this plan has none. Plan from a contract that has the old names, or leave out the statement.", void 0));
|
|
1683
|
+
const planner = new StatementPlanner({
|
|
1684
|
+
...input,
|
|
1685
|
+
fromContract
|
|
1686
|
+
});
|
|
1687
|
+
const appliedStatements = [];
|
|
1688
|
+
for (const statement of input.statements) {
|
|
1689
|
+
const operationIndexes = planner.plan(statement);
|
|
1690
|
+
if (!operationIndexes.ok) return operationIndexes;
|
|
1691
|
+
appliedStatements.push({
|
|
1692
|
+
statement,
|
|
1693
|
+
operationIndexes: operationIndexes.value
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
return ok({
|
|
1697
|
+
calls: planner.calls,
|
|
1698
|
+
tableRenames: planner.tableRenames,
|
|
1699
|
+
columnRenames: planner.columnRenames,
|
|
1700
|
+
appliedStatements
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
/** What a codec hook's call is about: the column of the field event it was returned for. */
|
|
1704
|
+
function fieldEventStorage(field) {
|
|
1705
|
+
return {
|
|
1706
|
+
storageName: [
|
|
1707
|
+
...field.namespaceId === UNBOUND_NAMESPACE_ID ? [] : [field.namespaceId],
|
|
1708
|
+
field.tableName,
|
|
1709
|
+
field.columnName
|
|
1710
|
+
].join("."),
|
|
1711
|
+
table: {
|
|
1712
|
+
namespaceId: field.namespaceId,
|
|
1713
|
+
table: field.tableName,
|
|
1714
|
+
column: field.columnName
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Names for destructive calls a target cannot map to storage: each call's factory name, with a
|
|
1720
|
+
* `#n` ordinal when several calls of the plan share it, so each name picks out one call.
|
|
1721
|
+
*/
|
|
1722
|
+
function unknownCallNames(calls) {
|
|
1723
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1724
|
+
for (const call of calls) counts.set(call.factoryName, (counts.get(call.factoryName) ?? 0) + 1);
|
|
1725
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1726
|
+
return new Map(calls.map((call) => {
|
|
1727
|
+
const ordinal = (seen.get(call.factoryName) ?? 0) + 1;
|
|
1728
|
+
seen.set(call.factoryName, ordinal);
|
|
1729
|
+
return [call, (counts.get(call.factoryName) ?? 0) > 1 ? `${call.factoryName}#${ordinal}` : call.factoryName];
|
|
1730
|
+
}));
|
|
1731
|
+
}
|
|
1732
|
+
function originTable(context, namespaceId, table) {
|
|
1733
|
+
for (const statement of context.statements) {
|
|
1734
|
+
if (statement.entity !== "model") continue;
|
|
1735
|
+
const effect = modelRenameStorageEffect(statement, context.fromContract, context.contract);
|
|
1736
|
+
if (!effect.ok || effect.value.kind !== "renameTable") continue;
|
|
1737
|
+
const { rename } = effect.value;
|
|
1738
|
+
if (rename.namespaceId === namespaceId && rename.to === table) return rename.from;
|
|
1739
|
+
}
|
|
1740
|
+
return table;
|
|
1741
|
+
}
|
|
1742
|
+
function originColumn(context, namespaceId, table, column) {
|
|
1743
|
+
for (const statement of context.statements) {
|
|
1744
|
+
if (statement.entity !== "field") continue;
|
|
1745
|
+
const effect = fieldRenameStorageEffect(statement, context.fromContract, context.contract);
|
|
1746
|
+
if (!effect.ok || effect.value.kind !== "renameColumn") continue;
|
|
1747
|
+
const rename = effect.value;
|
|
1748
|
+
if (rename.table.namespaceId === namespaceId && rename.table.table === table && rename.to === column) return rename.from;
|
|
1749
|
+
}
|
|
1750
|
+
return column;
|
|
1751
|
+
}
|
|
1752
|
+
/** The models of `contract` that store `table`: a model and its variants share one. */
|
|
1753
|
+
function modelsStoring(contract, namespaceId, table) {
|
|
1754
|
+
return Object.entries(contract.domain.namespaces).flatMap(([modelNamespace, namespace]) => Object.entries(namespace.models).flatMap(([model, definition]) => {
|
|
1755
|
+
const { storage } = definition;
|
|
1756
|
+
if (!isSqlModelStorage(storage)) return [];
|
|
1757
|
+
if (storage.namespaceId !== namespaceId || storage.table !== table) return [];
|
|
1758
|
+
return [{
|
|
1759
|
+
coordinate: {
|
|
1760
|
+
namespaceId: asNamespaceId(modelNamespace),
|
|
1761
|
+
model
|
|
1762
|
+
},
|
|
1763
|
+
isRoot: definition.base === void 0,
|
|
1764
|
+
fields: storage.fields
|
|
1765
|
+
}];
|
|
1766
|
+
}));
|
|
1767
|
+
}
|
|
1768
|
+
function subjectOf(target, context) {
|
|
1769
|
+
const storage = {
|
|
1770
|
+
kind: "storage",
|
|
1771
|
+
name: target.storageName
|
|
1772
|
+
};
|
|
1773
|
+
const { fromContract } = context;
|
|
1774
|
+
if (target.table === void 0 || fromContract === null) return storage;
|
|
1775
|
+
const withOrigin = {
|
|
1776
|
+
...context,
|
|
1777
|
+
fromContract
|
|
1778
|
+
};
|
|
1779
|
+
const { namespaceId } = target.table;
|
|
1780
|
+
const table = originTable(withOrigin, namespaceId, target.table.table);
|
|
1781
|
+
const models = modelsStoring(fromContract, namespaceId, table);
|
|
1782
|
+
if (target.table.column === void 0) {
|
|
1783
|
+
const model = models.find(({ isRoot }) => isRoot) ?? models[0];
|
|
1784
|
+
return model === void 0 ? storage : {
|
|
1785
|
+
kind: "model",
|
|
1786
|
+
...model.coordinate
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
const column = originColumn(withOrigin, namespaceId, table, target.table.column);
|
|
1790
|
+
for (const model of [...models].sort((a, b) => Number(b.isRoot) - Number(a.isRoot))) {
|
|
1791
|
+
const field = Object.entries(model.fields).find(([, stored]) => stored.column === column)?.[0];
|
|
1792
|
+
if (field !== void 0) return {
|
|
1793
|
+
kind: "field",
|
|
1794
|
+
...model.coordinate,
|
|
1795
|
+
field
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
return storage;
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* The subject of each operation that loses data and each that widens access, at its position in
|
|
1802
|
+
* the plan. A subject is a model or a field of the origin contract when one stores it, found under
|
|
1803
|
+
* its origin name when a rename statement renamed its table or column earlier in the plan.
|
|
1804
|
+
*/
|
|
1805
|
+
function subjectsOfCalls(calls, context) {
|
|
1806
|
+
const dataLoss = [];
|
|
1807
|
+
const accessWidening = [];
|
|
1808
|
+
let operationIndex = 0;
|
|
1809
|
+
for (const call of calls) {
|
|
1810
|
+
for (const target of call.dataLoss) dataLoss.push({
|
|
1811
|
+
operationIndex,
|
|
1812
|
+
subject: subjectOf(target, context)
|
|
1813
|
+
});
|
|
1814
|
+
for (const target of call.accessWidening) accessWidening.push({
|
|
1815
|
+
operationIndex,
|
|
1816
|
+
subject: subjectOf(target, context),
|
|
1817
|
+
widens: target.widens
|
|
1818
|
+
});
|
|
1819
|
+
operationIndex += call.operationCount;
|
|
1820
|
+
}
|
|
1821
|
+
return {
|
|
1822
|
+
dataLoss,
|
|
1823
|
+
accessWidening
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1329
1826
|
const readOnlyEmptyObject = Object.freeze({});
|
|
1330
1827
|
function cloneRecord(value) {
|
|
1331
1828
|
if (value === readOnlyEmptyObject) return value;
|
|
@@ -1381,15 +1878,20 @@ function createMigrationPlan(options) {
|
|
|
1381
1878
|
...options.meta ? { meta: cloneRecord(options.meta) } : {}
|
|
1382
1879
|
});
|
|
1383
1880
|
}
|
|
1384
|
-
function plannerSuccess(plan, warnings) {
|
|
1881
|
+
function plannerSuccess(plan, appliedStatements, subjects, warnings) {
|
|
1385
1882
|
return Object.freeze({
|
|
1386
1883
|
kind: "success",
|
|
1387
1884
|
plan,
|
|
1885
|
+
appliedStatements: Object.freeze([...appliedStatements]),
|
|
1886
|
+
dataLoss: Object.freeze([...subjects.dataLoss]),
|
|
1887
|
+
accessWidening: Object.freeze([...subjects.accessWidening]),
|
|
1388
1888
|
...warnings && warnings.length > 0 ? { warnings: Object.freeze(warnings.map((conflict) => Object.freeze({
|
|
1389
1889
|
kind: conflict.kind,
|
|
1390
1890
|
summary: conflict.summary,
|
|
1391
1891
|
...conflict.why ? { why: conflict.why } : {},
|
|
1392
1892
|
...conflict.location ? { location: Object.freeze({ ...conflict.location }) } : {},
|
|
1893
|
+
...conflict.refusedStatement ? { refusedStatement: conflict.refusedStatement } : {},
|
|
1894
|
+
...conflict.refusedOperationClass ? { refusedOperationClass: conflict.refusedOperationClass } : {},
|
|
1393
1895
|
...conflict.meta ? { meta: cloneRecord(conflict.meta) } : {}
|
|
1394
1896
|
}))) } : {}
|
|
1395
1897
|
});
|
|
@@ -1402,6 +1904,8 @@ function plannerFailure(conflicts) {
|
|
|
1402
1904
|
summary: conflict.summary,
|
|
1403
1905
|
...conflict.why ? { why: conflict.why } : {},
|
|
1404
1906
|
...conflict.location ? { location: Object.freeze({ ...conflict.location }) } : {},
|
|
1907
|
+
...conflict.refusedStatement ? { refusedStatement: conflict.refusedStatement } : {},
|
|
1908
|
+
...conflict.refusedOperationClass ? { refusedOperationClass: conflict.refusedOperationClass } : {},
|
|
1405
1909
|
...conflict.meta ? { meta: cloneRecord(conflict.meta) } : {}
|
|
1406
1910
|
})))
|
|
1407
1911
|
});
|
|
@@ -1551,6 +2055,6 @@ function temporalCodecPresetWithPrecision(input) {
|
|
|
1551
2055
|
}
|
|
1552
2056
|
var control_default = new SqlFamilyDescriptor();
|
|
1553
2057
|
//#endregion
|
|
1554
|
-
export {
|
|
2058
|
+
export { temporalStringAuthoringPresets as A, resolveTableRenameAgainst as C, storageNameOfOperation as D, sqlTypeLookupsOf as E, unmatchedColumnRename as M, unmatchedTableRename as N, subjectsOfCalls as O, resolveColumnRenameAgainst as S, runnerSuccess as T, planFieldEventCalls as _, contractNamespaceToSchemaIR as a, plannerFailure as b, control_default as c, detectTableNameCaseChanges as d, fieldEventStorage as f, partitionIssuesByControlPolicy as g, partitionCallsByControlPolicy as h, assembleAuthoringContributions as i, unknownCallNames as j, temporalCodecPresetWithPrecision as k, createMigrationPlan as l, modelRenameStorageEffect as m, INIT_ADDITIVE_POLICY as n, contractToSchemaIR as o, fieldRenameStorageEffect as p, TABLE_NAME_CASE_CHANGED_CODE as r, controlPolicyForCall as s, COLUMN_RENAME_UNMATCHED_CODE as t, detectDestructiveChanges as u, planFieldEventOperations as v, runnerFailure as w, plannerSuccess as x, planStatements as y };
|
|
1555
2059
|
|
|
1556
|
-
//# sourceMappingURL=control-
|
|
2060
|
+
//# sourceMappingURL=control-BRbds4vn.mjs.map
|