@prisma-next/family-sql 0.14.0-dev.4 → 0.14.0-dev.40
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/{authoring-type-constructors-CjFfO6LM.mjs → authoring-type-constructors-CXd-8ydc.mjs} +7 -30
- package/dist/authoring-type-constructors-CXd-8ydc.mjs.map +1 -0
- package/dist/contract-to-schema-ir-S-evq8E6.mjs +264 -0
- package/dist/contract-to-schema-ir-S-evq8E6.mjs.map +1 -0
- package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-DHYFuOBy.d.mts} +12 -7
- package/dist/{control-adapter-Cmw9LvEP.d.mts.map → control-adapter-DHYFuOBy.d.mts.map} +1 -1
- package/dist/control-adapter.d.mts +1 -1
- package/dist/control.d.mts +18 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +81 -943
- package/dist/control.mjs.map +1 -1
- package/dist/{schema-verify.d.mts → diff.d.mts} +5 -5
- package/dist/diff.d.mts.map +1 -0
- package/dist/{verify-sql-schema-xT4udQLQ.mjs → diff.mjs} +169 -253
- package/dist/diff.mjs.map +1 -0
- package/dist/ir.d.mts +9 -6
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/migration.d.mts +11 -2
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +7 -0
- package/dist/migration.mjs.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/psl-infer.d.mts +80 -0
- package/dist/psl-infer.d.mts.map +1 -0
- package/dist/psl-infer.mjs +334 -0
- package/dist/psl-infer.mjs.map +1 -0
- package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-CYtXg_zs.mjs} +17 -20
- package/dist/sql-contract-serializer-CYtXg_zs.mjs.map +1 -0
- package/dist/{verify-sql-schema-thU-jKpf.d.mts → sql-schema-diff-6z36dZt6.d.mts} +44 -5
- package/dist/sql-schema-diff-6z36dZt6.d.mts.map +1 -0
- package/dist/{types-kgstZ_Zd.d.mts → types-CkOIJXxU.d.mts} +84 -17
- package/dist/types-CkOIJXxU.d.mts.map +1 -0
- package/package.json +23 -23
- package/src/core/authoring-entity-types.ts +12 -37
- package/src/core/control-adapter.ts +10 -5
- package/src/core/control-instance.ts +174 -41
- package/src/core/{schema-verify/verify-sql-schema.ts → diff/sql-schema-diff.ts} +208 -16
- package/src/core/{schema-verify → diff}/verify-helpers.ts +12 -0
- package/src/core/ir/sql-contract-serializer-base.ts +23 -55
- package/src/core/ir/sql-contract-serializer.ts +5 -7
- package/src/core/migrations/contract-to-schema-ir.ts +49 -12
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/schema-differ.ts +40 -0
- package/src/core/migrations/types.ts +43 -2
- package/src/core/psl-contract-infer/printer-config.ts +0 -6
- package/src/core/sql-migration.ts +12 -1
- package/src/exports/control.ts +1 -0
- package/src/exports/diff.ts +18 -0
- package/src/exports/psl-infer.ts +36 -0
- package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
- package/dist/schema-verify.d.mts.map +0 -1
- package/dist/schema-verify.mjs +0 -2
- package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
- package/dist/test-utils.d.mts +0 -2
- package/dist/test-utils.mjs +0 -2
- package/dist/types-kgstZ_Zd.d.mts.map +0 -1
- package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
- package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
- package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
- package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
- package/src/exports/schema-verify.ts +0 -18
- package/src/exports/test-utils.ts +0 -9
- /package/src/core/{schema-verify → diff}/control-verify-emit.ts +0 -0
- /package/src/core/{schema-verify → diff}/verifier-disposition.ts +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { i as verifySqlSchema, n as NativeTypeNormalizer, r as VerifySqlSchemaOptions } from "./
|
|
2
|
-
import { SchemaIssue } from "@prisma-next/framework-components/control";
|
|
1
|
+
import { a as verifySqlSchemaTree, i as verifySqlSchema, n as NativeTypeNormalizer, r as VerifySqlSchemaOptions } from "./sql-schema-diff-6z36dZt6.mjs";
|
|
3
2
|
import { ControlPolicy } from "@prisma-next/contract/types";
|
|
3
|
+
import { SchemaIssue } from "@prisma-next/framework-components/control";
|
|
4
4
|
import { SqlIndexIR, SqlUniqueIR } from "@prisma-next/sql-schema-ir/types";
|
|
5
5
|
|
|
6
|
-
//#region src/core/
|
|
6
|
+
//#region src/core/diff/verify-helpers.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* Compares two arrays of strings for equality (order-sensitive).
|
|
9
9
|
*/
|
|
@@ -35,5 +35,5 @@ declare function isUniqueConstraintSatisfied(uniques: readonly SqlUniqueIR[], in
|
|
|
35
35
|
*/
|
|
36
36
|
declare function isIndexSatisfied(indexes: readonly SqlIndexIR[], uniques: readonly SqlUniqueIR[], columns: readonly string[]): boolean;
|
|
37
37
|
//#endregion
|
|
38
|
-
export { type NativeTypeNormalizer, type VerifySqlSchemaOptions, arraysEqual, isIndexSatisfied, isUniqueConstraintSatisfied, verifySqlSchema };
|
|
39
|
-
//# sourceMappingURL=
|
|
38
|
+
export { type NativeTypeNormalizer, type VerifySqlSchemaOptions, arraysEqual, isIndexSatisfied, isUniqueConstraintSatisfied, verifySqlSchema, verifySqlSchemaTree };
|
|
39
|
+
//# sourceMappingURL=diff.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.mts","names":[],"sources":["../src/core/diff/verify-helpers.ts"],"mappings":";;;;;;;;;iBAgEgB,WAAA,CAAY,CAAA,qBAAsB,CAAoB;AA+BtE;;;;;;;;;;AAG4B;AAuB5B;AA1BA,iBAAgB,2BAAA,CACd,OAAA,WAAkB,WAAA,IAClB,OAAA,WAAkB,UAAU,IAC5B,OAAA;;;;;;;;;AA0B0B;;;;iBAHZ,gBAAA,CACd,OAAA,WAAkB,UAAA,IAClB,OAAA,WAAkB,WAAW,IAC7B,OAAA"}
|
|
@@ -1,241 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { a as extractCodecControlHooks, i as resolveValueSetValues } from "./contract-to-schema-ir-S-evq8E6.mjs";
|
|
2
|
+
import { blindCast } from "@prisma-next/utils/casts";
|
|
3
|
+
import { effectiveControlPolicy } from "@prisma-next/contract/types";
|
|
4
|
+
import { dispositionForCategory } from "@prisma-next/framework-components/control";
|
|
4
5
|
import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
|
|
6
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
5
7
|
import { StorageTable, isStorageTypeInstance } from "@prisma-next/sql-contract/types";
|
|
6
|
-
import { effectiveControlPolicy } from "@prisma-next/contract/types";
|
|
7
8
|
import { canonicalStringify } from "@prisma-next/utils/canonical-stringify";
|
|
8
|
-
//#region src/core/
|
|
9
|
-
function hasCodecControlHooks(descriptor) {
|
|
10
|
-
if (typeof descriptor !== "object" || descriptor === null) return false;
|
|
11
|
-
const hooks = descriptor.types?.codecTypes?.controlPlaneHooks;
|
|
12
|
-
return hooks !== null && hooks !== void 0 && typeof hooks === "object";
|
|
13
|
-
}
|
|
14
|
-
function extractCodecControlHooks(descriptors) {
|
|
15
|
-
const hooks = /* @__PURE__ */ new Map();
|
|
16
|
-
const owners = /* @__PURE__ */ new Map();
|
|
17
|
-
for (const descriptor of descriptors) {
|
|
18
|
-
if (typeof descriptor !== "object" || descriptor === null) continue;
|
|
19
|
-
if (!hasCodecControlHooks(descriptor)) continue;
|
|
20
|
-
const controlPlaneHooks = descriptor.types.codecTypes.controlPlaneHooks;
|
|
21
|
-
for (const [codecId, hook] of Object.entries(controlPlaneHooks)) {
|
|
22
|
-
assertUniqueCodecOwner({
|
|
23
|
-
codecId,
|
|
24
|
-
owners,
|
|
25
|
-
descriptorId: descriptor.id,
|
|
26
|
-
entityLabel: "control hooks",
|
|
27
|
-
entityOwnershipLabel: "owner"
|
|
28
|
-
});
|
|
29
|
-
hooks.set(codecId, hook);
|
|
30
|
-
owners.set(codecId, descriptor.id);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return hooks;
|
|
34
|
-
}
|
|
35
|
-
//#endregion
|
|
36
|
-
//#region src/core/migrations/contract-to-schema-ir.ts
|
|
37
|
-
function convertColumn(name, column, storageTypes, expandNativeType, renderDefault) {
|
|
38
|
-
const resolved = resolveColumnTypeMetadata(column, storageTypes);
|
|
39
|
-
return {
|
|
40
|
-
name,
|
|
41
|
-
nativeType: expandNativeType ? expandNativeType({
|
|
42
|
-
nativeType: resolved.nativeType,
|
|
43
|
-
codecId: resolved.codecId,
|
|
44
|
-
...ifDefined("typeParams", resolved.typeParams)
|
|
45
|
-
}) : resolved.nativeType,
|
|
46
|
-
nullable: column.nullable,
|
|
47
|
-
...ifDefined("default", column.default != null && renderDefault ? renderDefault(column.default, column) : void 0)
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function resolveColumnTypeMetadata(column, storageTypes) {
|
|
51
|
-
if (!column.typeRef) return column;
|
|
52
|
-
const referenced = storageTypes[column.typeRef];
|
|
53
|
-
if (!referenced) throw new Error(`Column references storage type "${column.typeRef}" but it is not defined in storage.types.`);
|
|
54
|
-
if (isStorageTypeInstance(referenced)) return {
|
|
55
|
-
codecId: referenced.codecId,
|
|
56
|
-
nativeType: referenced.nativeType,
|
|
57
|
-
typeParams: referenced.typeParams
|
|
58
|
-
};
|
|
59
|
-
throw new Error(`Storage type "${column.typeRef}" has an unknown polymorphic kind; expected a codec-typed StorageTypeInstance.`);
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Resolves a `ValueSetRef` to its permitted values from the contract storage.
|
|
63
|
-
*
|
|
64
|
-
* Throws when the referenced namespace or value-set is absent — this indicates
|
|
65
|
-
* the contract was built incorrectly (the check and the value-set must be
|
|
66
|
-
* co-emitted by the lowering step). Used by `convertCheck` (schema-IR
|
|
67
|
-
* projection), `verifyCheckConstraints` (verification), and
|
|
68
|
-
* `checkConstraintPlanCallStrategy` (migration planning) so all three agree on
|
|
69
|
-
* the resolved values and the error behavior on a missing reference.
|
|
70
|
-
*/
|
|
71
|
-
function allStrings(values) {
|
|
72
|
-
return values.every((value) => typeof value === "string");
|
|
73
|
-
}
|
|
74
|
-
function resolveValueSetValues(ref, storage, contextLabel) {
|
|
75
|
-
const ns = storage.namespaces[ref.namespaceId];
|
|
76
|
-
if (!ns) throw new Error(`resolveValueSetValues: namespace "${ref.namespaceId}" not found in storage (${contextLabel})`);
|
|
77
|
-
const valueSet = ns.entries.valueSet?.[ref.entityName];
|
|
78
|
-
if (!valueSet) throw new Error(`resolveValueSetValues: value-set "${ref.entityName}" not found in namespace "${ref.namespaceId}" (${contextLabel})`);
|
|
79
|
-
const values = valueSet.values;
|
|
80
|
-
if (!allStrings(values)) throw new Error(`resolveValueSetValues: value-set "${ref.entityName}" in namespace "${ref.namespaceId}" has a non-string value; numeric-enum CHECK constraints are not yet supported (${contextLabel})`);
|
|
81
|
-
return values;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Projects a `CheckConstraint` IR into an `SqlCheckConstraintIRInput` by
|
|
85
|
-
* resolving the permitted values from the storage value-set it references.
|
|
86
|
-
*
|
|
87
|
-
* The `CheckConstraint.valueSet` ref points to
|
|
88
|
-
* `storage.namespaces[namespaceId].entries.valueSet[name]`. The resolved
|
|
89
|
-
* values are lifted directly from `StorageValueSet.values` so verification
|
|
90
|
-
* compares value sets, not SQL predicate strings.
|
|
91
|
-
*
|
|
92
|
-
* Throws if the referenced namespace or value-set is absent — this
|
|
93
|
-
* indicates the contract was built incorrectly (the check and the
|
|
94
|
-
* value-set must be co-emitted by the lowering step).
|
|
95
|
-
*/
|
|
96
|
-
function convertCheck(check, storage) {
|
|
97
|
-
const permittedValues = resolveValueSetValues(check.valueSet, storage, `check "${check.name}"`);
|
|
98
|
-
return {
|
|
99
|
-
name: check.name,
|
|
100
|
-
column: check.column,
|
|
101
|
-
permittedValues
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
function convertUnique(unique) {
|
|
105
|
-
return {
|
|
106
|
-
columns: unique.columns,
|
|
107
|
-
...ifDefined("name", unique.name)
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function convertIndex(index) {
|
|
111
|
-
return {
|
|
112
|
-
columns: index.columns,
|
|
113
|
-
unique: false,
|
|
114
|
-
...ifDefined("name", index.name)
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
function convertForeignKey(fk) {
|
|
118
|
-
return {
|
|
119
|
-
columns: fk.source.columns,
|
|
120
|
-
referencedTable: fk.target.tableName,
|
|
121
|
-
referencedSchema: fk.target.namespaceId,
|
|
122
|
-
referencedColumns: fk.target.columns,
|
|
123
|
-
...ifDefined("name", fk.name),
|
|
124
|
-
...ifDefined("onDelete", fk.onDelete),
|
|
125
|
-
...ifDefined("onUpdate", fk.onUpdate)
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
function convertTable(name, table, storageTypes, expandNativeType, renderDefault, storage) {
|
|
129
|
-
const columns = {};
|
|
130
|
-
for (const [colName, colDef] of Object.entries(table.columns)) columns[colName] = convertColumn(colName, colDef, storageTypes, expandNativeType, renderDefault);
|
|
131
|
-
const satisfiedIndexColumns = new Set([
|
|
132
|
-
...table.indexes.map((idx) => idx.columns.join(",")),
|
|
133
|
-
...table.uniques.map((unique) => unique.columns.join(",")),
|
|
134
|
-
...table.primaryKey ? [table.primaryKey.columns.join(",")] : []
|
|
135
|
-
]);
|
|
136
|
-
const fkBackingIndexes = [];
|
|
137
|
-
for (const fk of table.foreignKeys) {
|
|
138
|
-
if (fk.index === false) continue;
|
|
139
|
-
const key = fk.source.columns.join(",");
|
|
140
|
-
if (satisfiedIndexColumns.has(key)) continue;
|
|
141
|
-
fkBackingIndexes.push({
|
|
142
|
-
columns: fk.source.columns,
|
|
143
|
-
unique: false,
|
|
144
|
-
name: defaultIndexName(name, fk.source.columns)
|
|
145
|
-
});
|
|
146
|
-
satisfiedIndexColumns.add(key);
|
|
147
|
-
}
|
|
148
|
-
const checks = table.checks && table.checks.length > 0 ? table.checks.map((c) => convertCheck(c, storage)) : void 0;
|
|
149
|
-
return {
|
|
150
|
-
name,
|
|
151
|
-
columns,
|
|
152
|
-
...ifDefined("primaryKey", table.primaryKey),
|
|
153
|
-
foreignKeys: table.foreignKeys.filter((fk) => fk.constraint !== false).map(convertForeignKey),
|
|
154
|
-
uniques: table.uniques.map(convertUnique),
|
|
155
|
-
indexes: [...table.indexes.map(convertIndex), ...fkBackingIndexes],
|
|
156
|
-
...ifDefined("checks", checks)
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Detects destructive changes between two contract storages.
|
|
161
|
-
*
|
|
162
|
-
* The additive-only planner silently ignores removals (tables, columns).
|
|
163
|
-
* This function detects those removals so callers can report them as conflicts
|
|
164
|
-
* rather than silently producing an empty plan.
|
|
165
|
-
*
|
|
166
|
-
* Returns an empty array if no destructive changes are found.
|
|
167
|
-
*/
|
|
168
|
-
function detectDestructiveChanges(from, to) {
|
|
169
|
-
if (!from) return [];
|
|
170
|
-
const hasOwn = (value, key) => Object.hasOwn(value, key);
|
|
171
|
-
const conflicts = [];
|
|
172
|
-
const namespaceIds = [...new Set([...Object.keys(from.namespaces), ...Object.keys(to.namespaces)])].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
173
|
-
for (const namespaceId of namespaceIds) {
|
|
174
|
-
const fromNs = from.namespaces[namespaceId];
|
|
175
|
-
const toNs = to.namespaces[namespaceId];
|
|
176
|
-
const fromTables = fromNs?.entries.table;
|
|
177
|
-
if (!fromTables) continue;
|
|
178
|
-
for (const tableName of Object.keys(fromTables)) {
|
|
179
|
-
const toTableRaw = toNs?.entries.table?.[tableName];
|
|
180
|
-
if (!(toTableRaw instanceof StorageTable)) {
|
|
181
|
-
conflicts.push({
|
|
182
|
-
kind: "tableRemoved",
|
|
183
|
-
summary: `Table "${tableName}" was removed`
|
|
184
|
-
});
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const toTable = toTableRaw;
|
|
188
|
-
const fromTableRaw = fromTables[tableName];
|
|
189
|
-
if (!(fromTableRaw instanceof StorageTable)) continue;
|
|
190
|
-
const fromTable = fromTableRaw;
|
|
191
|
-
for (const columnName of Object.keys(fromTable.columns)) if (!hasOwn(toTable.columns, columnName)) conflicts.push({
|
|
192
|
-
kind: "columnRemoved",
|
|
193
|
-
summary: `Column "${tableName}"."${columnName}" was removed`
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return conflicts;
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Converts a `Contract` to `SqlSchemaIR`.
|
|
201
|
-
*
|
|
202
|
-
* Reads `contract.storage` for tables and `contract.storage.types` for type
|
|
203
|
-
* annotations. Storage-type annotations are written under
|
|
204
|
-
* `options.annotationNamespace`.
|
|
205
|
-
*
|
|
206
|
-
* Drops codec metadata (`codecId`, `typeRef`) since the schema IR only represents
|
|
207
|
-
* structural information. When `expandNativeType` is provided, parameterized types
|
|
208
|
-
* are expanded (e.g. `character` + `{ length: 36 }` → `character(36)`) so the
|
|
209
|
-
* resulting IR compares correctly against the "to" contract during planning.
|
|
210
|
-
*
|
|
211
|
-
* Returns an empty schema IR when `contract` is `null` (new project).
|
|
212
|
-
*/
|
|
213
|
-
function contractToSchemaIR(contract, options) {
|
|
214
|
-
if (options.annotationNamespace.length === 0) throw new Error("annotationNamespace must be a non-empty string");
|
|
215
|
-
if (!contract) return { tables: {} };
|
|
216
|
-
const storage = contract.storage;
|
|
217
|
-
const storageTypes = { ...storage.types ?? {} };
|
|
218
|
-
const tables = {};
|
|
219
|
-
for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table ?? {})) {
|
|
220
|
-
if (!(tableDefRaw instanceof StorageTable)) throw new Error(`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.entries.table.${tableName}`);
|
|
221
|
-
const tableDef = tableDefRaw;
|
|
222
|
-
if (tables[tableName] !== void 0) throw new Error(`contractToSchemaIR: duplicate SQL table name "${tableName}" across namespaces (ambiguous for flat SqlSchemaIR.tables).`);
|
|
223
|
-
tables[tableName] = convertTable(tableName, tableDef, storageTypes, options.expandNativeType, options.renderDefault, storage);
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
tables,
|
|
227
|
-
...ifDefined("annotations", deriveAnnotations(storage, options.annotationNamespace, options.resolveEnumNamespaceSchema))
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
function deriveAnnotations(storage, annotationNamespace, _resolveEnumNamespaceSchema) {
|
|
231
|
-
const storageTypes = {};
|
|
232
|
-
for (const typeInstance of Object.values(storage.types ?? {})) if (isStorageTypeInstance(typeInstance)) storageTypes[typeInstance.nativeType] = typeInstance;
|
|
233
|
-
const envelope = { ...Object.keys(storageTypes).length > 0 ? { storageTypes } : {} };
|
|
234
|
-
if (Object.keys(envelope).length === 0) return void 0;
|
|
235
|
-
return { [annotationNamespace]: envelope };
|
|
236
|
-
}
|
|
237
|
-
//#endregion
|
|
238
|
-
//#region src/core/schema-verify/verifier-disposition.ts
|
|
9
|
+
//#region src/core/diff/verifier-disposition.ts
|
|
239
10
|
/**
|
|
240
11
|
* Classifies the relational verifier issue kinds the SQL family emits (tables,
|
|
241
12
|
* columns, constraints, indexes, defaults, enum types) into the target-neutral
|
|
@@ -275,7 +46,7 @@ function verifierDisposition(controlPolicy, issueKind) {
|
|
|
275
46
|
return dispositionForCategory(controlPolicy, classifySqlVerifierIssueKind(issueKind));
|
|
276
47
|
}
|
|
277
48
|
//#endregion
|
|
278
|
-
//#region src/core/
|
|
49
|
+
//#region src/core/diff/control-verify-emit.ts
|
|
279
50
|
/**
|
|
280
51
|
* Grades `issue` under `controlPolicy` and, unless suppressed, pushes both the
|
|
281
52
|
* issue and a status-stamped verification node. Returns the resolved outcome so
|
|
@@ -303,7 +74,7 @@ function emitIssueUnderControlPolicy(controlPolicy, issue, issues) {
|
|
|
303
74
|
return disposition;
|
|
304
75
|
}
|
|
305
76
|
//#endregion
|
|
306
|
-
//#region src/core/
|
|
77
|
+
//#region src/core/diff/verify-helpers.ts
|
|
307
78
|
function indexOptionsLooselyEqual(a, b) {
|
|
308
79
|
const aKeys = a ? Object.keys(a).sort() : [];
|
|
309
80
|
const bKeys = b ? Object.keys(b).sort() : [];
|
|
@@ -368,6 +139,7 @@ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableCon
|
|
|
368
139
|
if (!schemaPK) {
|
|
369
140
|
const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
|
|
370
141
|
kind: "primary_key_mismatch",
|
|
142
|
+
reason: "not-equal",
|
|
371
143
|
table: tableName,
|
|
372
144
|
namespaceId,
|
|
373
145
|
expected: contractPK.columns.join(", "),
|
|
@@ -378,6 +150,7 @@ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableCon
|
|
|
378
150
|
if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
|
|
379
151
|
const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
|
|
380
152
|
kind: "primary_key_mismatch",
|
|
153
|
+
reason: "not-equal",
|
|
381
154
|
table: tableName,
|
|
382
155
|
namespaceId,
|
|
383
156
|
expected: contractPK.columns.join(", "),
|
|
@@ -405,6 +178,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
|
|
|
405
178
|
});
|
|
406
179
|
if (!matchingFK) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
407
180
|
kind: "foreign_key_mismatch",
|
|
181
|
+
reason: "not-equal",
|
|
408
182
|
table: tableName,
|
|
409
183
|
namespaceId,
|
|
410
184
|
expected: `${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`,
|
|
@@ -428,6 +202,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
|
|
|
428
202
|
const combinedActual = actionMismatches.map((m) => m.actual).join(", ");
|
|
429
203
|
emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
430
204
|
kind: "foreign_key_mismatch",
|
|
205
|
+
reason: "not-equal",
|
|
431
206
|
table: tableName,
|
|
432
207
|
namespaceId,
|
|
433
208
|
indexOrConstraint: matchingFK.name ?? `fk(${contractFK.source.columns.join(",")})`,
|
|
@@ -464,6 +239,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
|
|
|
464
239
|
return arraysEqual(fk.source.columns, schemaFK.columns) && tablesMatch && arraysEqual(fk.target.columns, schemaFK.referencedColumns);
|
|
465
240
|
})) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
466
241
|
kind: "extra_foreign_key",
|
|
242
|
+
reason: "not-expected",
|
|
467
243
|
table: tableName,
|
|
468
244
|
namespaceId,
|
|
469
245
|
indexOrConstraint: schemaFK.name ?? `fk(${schemaFK.columns.join(",")})`,
|
|
@@ -501,6 +277,7 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
|
|
|
501
277
|
const matchingUniqueIndex = !matchingUnique && schemaIndexes.find((idx) => idx.unique && arraysEqual(idx.columns, contractUnique.columns));
|
|
502
278
|
if (!matchingUnique && !matchingUniqueIndex) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
503
279
|
kind: "unique_constraint_mismatch",
|
|
280
|
+
reason: "not-equal",
|
|
504
281
|
table: tableName,
|
|
505
282
|
namespaceId,
|
|
506
283
|
expected: contractUnique.columns.join(", "),
|
|
@@ -531,6 +308,7 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
|
|
|
531
308
|
if (strict) {
|
|
532
309
|
for (const schemaUnique of schemaUniques) if (!contractUniques.find((u) => arraysEqual(u.columns, schemaUnique.columns))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
533
310
|
kind: "extra_unique_constraint",
|
|
311
|
+
reason: "not-expected",
|
|
534
312
|
table: tableName,
|
|
535
313
|
namespaceId,
|
|
536
314
|
indexOrConstraint: schemaUnique.name ?? `unique(${schemaUnique.columns.join(",")})`,
|
|
@@ -568,6 +346,7 @@ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName,
|
|
|
568
346
|
const matchingUniqueConstraint = !matchingIndex && contractIndex.type === void 0 && contractIndex.options === void 0 && schemaUniques.find((u) => arraysEqual(u.columns, contractIndex.columns));
|
|
569
347
|
if (!matchingIndex && !matchingUniqueConstraint) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
570
348
|
kind: "index_mismatch",
|
|
349
|
+
reason: "not-equal",
|
|
571
350
|
table: tableName,
|
|
572
351
|
namespaceId,
|
|
573
352
|
expected: contractIndex.columns.join(", "),
|
|
@@ -599,6 +378,7 @@ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName,
|
|
|
599
378
|
if (schemaIndex.unique) continue;
|
|
600
379
|
if (!contractIndexes.find((idx) => arraysEqual(idx.columns, schemaIndex.columns) && indexExtrasMatch(idx, schemaIndex))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
601
380
|
kind: "extra_index",
|
|
381
|
+
reason: "not-expected",
|
|
602
382
|
table: tableName,
|
|
603
383
|
namespaceId,
|
|
604
384
|
indexOrConstraint: schemaIndex.name ?? `idx(${schemaIndex.columns.join(",")})`,
|
|
@@ -707,6 +487,7 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
|
|
|
707
487
|
const liveCheck = schemaChecks.find((c) => c.name === contractCheck.name);
|
|
708
488
|
if (!liveCheck) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
709
489
|
kind: "check_missing",
|
|
490
|
+
reason: "not-found",
|
|
710
491
|
table: tableName,
|
|
711
492
|
namespaceId,
|
|
712
493
|
indexOrConstraint: contractCheck.name,
|
|
@@ -725,6 +506,7 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
|
|
|
725
506
|
}, issues, nodes);
|
|
726
507
|
else if (!valueSetsEqual(contractCheck.permittedValues, liveCheck.permittedValues)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
727
508
|
kind: "check_mismatch",
|
|
509
|
+
reason: "not-equal",
|
|
728
510
|
table: tableName,
|
|
729
511
|
namespaceId,
|
|
730
512
|
indexOrConstraint: contractCheck.name,
|
|
@@ -757,6 +539,7 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
|
|
|
757
539
|
if (strict) {
|
|
758
540
|
for (const liveCheck of schemaChecks) if (!contractChecks.find((c) => c.name === liveCheck.name)) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
759
541
|
kind: "check_removed",
|
|
542
|
+
reason: "not-equal",
|
|
760
543
|
table: tableName,
|
|
761
544
|
namespaceId,
|
|
762
545
|
indexOrConstraint: liveCheck.name,
|
|
@@ -777,7 +560,20 @@ function verifyCheckConstraints(contractChecks, schemaChecks, tableName, namespa
|
|
|
777
560
|
return nodes;
|
|
778
561
|
}
|
|
779
562
|
//#endregion
|
|
780
|
-
//#region src/core/
|
|
563
|
+
//#region src/core/diff/sql-schema-diff.ts
|
|
564
|
+
/**
|
|
565
|
+
* Returns the per-schema namespace nodes of an introspected schema node, for
|
|
566
|
+
* the relational verify to consume one at a time. Structure-agnostic — imports
|
|
567
|
+
* no target node class. A root exposing a `namespaces` record (Postgres) yields
|
|
568
|
+
* its namespace nodes (never merged, so same-named tables in different schemas
|
|
569
|
+
* cannot collide); a flat schema (SQLite) is its own single namespace and
|
|
570
|
+
* yields itself. Handles spread-flattened input (own-enumerable fields survive).
|
|
571
|
+
*/
|
|
572
|
+
function namespaceSchemaNodes(schema) {
|
|
573
|
+
const obj = blindCast(schema);
|
|
574
|
+
if (obj.namespaces !== void 0) return Object.values(obj.namespaces);
|
|
575
|
+
return [blindCast(schema)];
|
|
576
|
+
}
|
|
781
577
|
/**
|
|
782
578
|
* Verifies that a SqlSchemaIR matches a Contract.
|
|
783
579
|
*
|
|
@@ -801,7 +597,8 @@ function verifySqlSchema(options) {
|
|
|
801
597
|
codecHooks,
|
|
802
598
|
storageTypes: contract.storage.types ?? {},
|
|
803
599
|
...ifDefined("normalizeDefault", normalizeDefault),
|
|
804
|
-
...ifDefined("normalizeNativeType", normalizeNativeType)
|
|
600
|
+
...ifDefined("normalizeNativeType", normalizeNativeType),
|
|
601
|
+
...ifDefined("restrictToNamespaceIds", options.restrictToNamespaceIds)
|
|
805
602
|
});
|
|
806
603
|
validateFrameworkComponentsForExtensions(contract, options.frameworkComponents);
|
|
807
604
|
const typeNodes = [];
|
|
@@ -872,6 +669,7 @@ function verifySqlSchema(options) {
|
|
|
872
669
|
},
|
|
873
670
|
schema: {
|
|
874
671
|
issues,
|
|
672
|
+
schemaDiffIssues: [],
|
|
875
673
|
root,
|
|
876
674
|
counts
|
|
877
675
|
},
|
|
@@ -891,17 +689,18 @@ function extractContractMetadata(contract) {
|
|
|
891
689
|
};
|
|
892
690
|
}
|
|
893
691
|
function verifySchemaTables(options) {
|
|
894
|
-
const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
692
|
+
const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, restrictToNamespaceIds } = options;
|
|
895
693
|
const contractDefaultControl = contract.defaultControlPolicy;
|
|
896
694
|
const issues = [];
|
|
897
695
|
const rootChildren = [];
|
|
898
696
|
const schemaTables = schema.tables;
|
|
899
697
|
const namespaceIds = Object.keys(contract.storage.namespaces).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
900
698
|
for (const namespaceId of namespaceIds) {
|
|
699
|
+
if (restrictToNamespaceIds !== void 0 && !restrictToNamespaceIds.has(namespaceId)) continue;
|
|
901
700
|
const ns = contract.storage.namespaces[namespaceId];
|
|
902
701
|
if (!ns) continue;
|
|
903
702
|
for (const [tableName, contractTableRaw] of Object.entries(ns.entries.table ?? {})) {
|
|
904
|
-
|
|
703
|
+
StorageTable.assert(contractTableRaw, `storage.namespaces.${namespaceId}.entries.table.${tableName}`);
|
|
905
704
|
const contractTable = contractTableRaw;
|
|
906
705
|
const tableControlPolicy = effectiveControlPolicy(contractTable.control, contractDefaultControl);
|
|
907
706
|
const schemaTable = schemaTables[tableName];
|
|
@@ -909,6 +708,7 @@ function verifySchemaTables(options) {
|
|
|
909
708
|
if (!schemaTable) {
|
|
910
709
|
emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
911
710
|
kind: "missing_table",
|
|
711
|
+
reason: "not-found",
|
|
912
712
|
table: tableName,
|
|
913
713
|
namespaceId,
|
|
914
714
|
message: `Table "${tableName}" is missing from database`
|
|
@@ -947,6 +747,7 @@ function verifySchemaTables(options) {
|
|
|
947
747
|
if (strict) {
|
|
948
748
|
for (const tableName of Object.keys(schemaTables)) if (!namespaceIds.some((namespaceId) => contract.storage.namespaces[namespaceId]?.entries.table?.[tableName] !== void 0)) emitIssueAndNodeUnderControlPolicy(effectiveControlPolicy(void 0, contractDefaultControl), {
|
|
949
749
|
kind: "extra_table",
|
|
750
|
+
reason: "not-expected",
|
|
950
751
|
table: tableName,
|
|
951
752
|
message: `Extra table "${tableName}" found in database (not in contract)`
|
|
952
753
|
}, {
|
|
@@ -958,7 +759,8 @@ function verifySchemaTables(options) {
|
|
|
958
759
|
message: `Extra table "${tableName}" found`,
|
|
959
760
|
expected: void 0,
|
|
960
761
|
actual: void 0,
|
|
961
|
-
children: []
|
|
762
|
+
children: [],
|
|
763
|
+
reason: "not-expected"
|
|
962
764
|
}, issues, rootChildren);
|
|
963
765
|
}
|
|
964
766
|
return {
|
|
@@ -1032,6 +834,7 @@ function verifyTableChildren(options) {
|
|
|
1032
834
|
});
|
|
1033
835
|
} else if (schemaTable.primaryKey && strict) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
1034
836
|
kind: "extra_primary_key",
|
|
837
|
+
reason: "not-expected",
|
|
1035
838
|
table: tableName,
|
|
1036
839
|
namespaceId,
|
|
1037
840
|
message: "Extra primary key found in database (not in contract)"
|
|
@@ -1076,6 +879,7 @@ function collectContractColumnNodes(options) {
|
|
|
1076
879
|
if (!schemaColumn) {
|
|
1077
880
|
emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
1078
881
|
kind: "missing_column",
|
|
882
|
+
reason: "not-found",
|
|
1079
883
|
table: tableName,
|
|
1080
884
|
namespaceId,
|
|
1081
885
|
column: columnName,
|
|
@@ -1116,6 +920,7 @@ function appendExtraColumnNodes(options) {
|
|
|
1116
920
|
const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, columnNodes } = options;
|
|
1117
921
|
for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
1118
922
|
kind: "extra_column",
|
|
923
|
+
reason: "not-expected",
|
|
1119
924
|
table: tableName,
|
|
1120
925
|
namespaceId,
|
|
1121
926
|
column: columnName,
|
|
@@ -1144,10 +949,12 @@ function verifyColumn(options) {
|
|
|
1144
949
|
tableName,
|
|
1145
950
|
columnName
|
|
1146
951
|
});
|
|
1147
|
-
const
|
|
952
|
+
const schemaBaseNativeType = normalizeNativeType?.(schemaColumn.nativeType) ?? schemaColumn.nativeType;
|
|
953
|
+
const schemaNativeType = schemaColumn.many ? `${schemaBaseNativeType}[]` : schemaBaseNativeType;
|
|
1148
954
|
if (!(contractNativeType === schemaNativeType)) {
|
|
1149
955
|
const issue = {
|
|
1150
956
|
kind: "type_mismatch",
|
|
957
|
+
reason: "not-equal",
|
|
1151
958
|
table: tableName,
|
|
1152
959
|
namespaceId,
|
|
1153
960
|
column: columnName,
|
|
@@ -1200,6 +1007,7 @@ function verifyColumn(options) {
|
|
|
1200
1007
|
if (contractColumn.nullable !== schemaColumn.nullable) {
|
|
1201
1008
|
const issue = {
|
|
1202
1009
|
kind: "nullability_mismatch",
|
|
1010
|
+
reason: "not-equal",
|
|
1203
1011
|
table: tableName,
|
|
1204
1012
|
namespaceId,
|
|
1205
1013
|
column: columnName,
|
|
@@ -1229,6 +1037,7 @@ function verifyColumn(options) {
|
|
|
1229
1037
|
const defaultDescription = describeColumnDefault(contractColumn.default);
|
|
1230
1038
|
const issue = {
|
|
1231
1039
|
kind: "default_missing",
|
|
1040
|
+
reason: "not-found",
|
|
1232
1041
|
table: tableName,
|
|
1233
1042
|
namespaceId,
|
|
1234
1043
|
column: columnName,
|
|
@@ -1256,6 +1065,7 @@ function verifyColumn(options) {
|
|
|
1256
1065
|
const actualDescription = schemaColumn.default;
|
|
1257
1066
|
const issue = {
|
|
1258
1067
|
kind: "default_mismatch",
|
|
1068
|
+
reason: "not-equal",
|
|
1259
1069
|
table: tableName,
|
|
1260
1070
|
namespaceId,
|
|
1261
1071
|
column: columnName,
|
|
@@ -1283,6 +1093,7 @@ function verifyColumn(options) {
|
|
|
1283
1093
|
} else if (strict && schemaColumn.default) {
|
|
1284
1094
|
const issue = {
|
|
1285
1095
|
kind: "extra_default",
|
|
1096
|
+
reason: "not-expected",
|
|
1286
1097
|
table: tableName,
|
|
1287
1098
|
namespaceId,
|
|
1288
1099
|
column: columnName,
|
|
@@ -1401,14 +1212,17 @@ function validateFrameworkComponentsForExtensions(contract, frameworkComponents)
|
|
|
1401
1212
|
*/
|
|
1402
1213
|
function renderExpectedNativeType(contractColumn, storageTypes, codecHooks, context) {
|
|
1403
1214
|
const { codecId, nativeType, typeParams } = resolveContractColumnTypeMetadata(contractColumn, storageTypes, context);
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1215
|
+
let baseType;
|
|
1216
|
+
if (!typeParams || !codecId) baseType = nativeType;
|
|
1217
|
+
else {
|
|
1218
|
+
const hooks = codecHooks.get(codecId);
|
|
1219
|
+
baseType = hooks?.expandNativeType ? hooks.expandNativeType({
|
|
1220
|
+
nativeType,
|
|
1221
|
+
codecId,
|
|
1222
|
+
typeParams
|
|
1223
|
+
}) : nativeType;
|
|
1224
|
+
}
|
|
1225
|
+
return contractColumn.many ? `${baseType}[]` : baseType;
|
|
1412
1226
|
}
|
|
1413
1227
|
function resolveContractColumnTypeMetadata(contractColumn, storageTypes, context) {
|
|
1414
1228
|
if (!contractColumn.typeRef) return contractColumn;
|
|
@@ -1495,7 +1309,109 @@ function formatLiteralValue(value) {
|
|
|
1495
1309
|
if (typeof value === "string") return value;
|
|
1496
1310
|
return JSON.stringify(value);
|
|
1497
1311
|
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Reads a namespace node's DDL schema name. Namespaced target nodes (Postgres
|
|
1314
|
+
* `PostgresNamespaceSchemaNode`) carry `schemaName`; a flat schema (SQLite) has
|
|
1315
|
+
* none and pairs by position as the sole namespace.
|
|
1316
|
+
*/
|
|
1317
|
+
function namespaceSchemaName(node) {
|
|
1318
|
+
return blindCast(node).schemaName;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Returns a shallow copy of `contract` exposing only the one named namespace —
|
|
1322
|
+
* used solely to resolve that namespace's live DDL schema via the target's
|
|
1323
|
+
* expected-tree projection (never for verification, so global value-sets it may
|
|
1324
|
+
* reference are not consulted).
|
|
1325
|
+
*/
|
|
1326
|
+
function scopeContractToNamespace(contract, namespaceId) {
|
|
1327
|
+
const namespace = contract.storage.namespaces[namespaceId];
|
|
1328
|
+
const scopedNamespaces = namespace === void 0 ? {} : { [namespaceId]: namespace };
|
|
1329
|
+
return blindCast({
|
|
1330
|
+
...contract,
|
|
1331
|
+
storage: blindCast({
|
|
1332
|
+
...contract.storage,
|
|
1333
|
+
namespaces: scopedNamespaces
|
|
1334
|
+
})
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Combines two `VerifyDatabaseSchemaResult`s by concatenating issues and summing
|
|
1339
|
+
* counts — used to fold the per-namespace pairings of a multi-schema database
|
|
1340
|
+
* into one result. The verification-tree `root` of the first pairing is
|
|
1341
|
+
* retained (multi-schema verify-tree shaping is future work).
|
|
1342
|
+
*/
|
|
1343
|
+
function mergeVerifyResults(a, b) {
|
|
1344
|
+
return {
|
|
1345
|
+
...a,
|
|
1346
|
+
ok: a.ok && b.ok,
|
|
1347
|
+
...ifDefined("code", a.code ?? b.code),
|
|
1348
|
+
schema: {
|
|
1349
|
+
...a.schema,
|
|
1350
|
+
issues: [...a.schema.issues, ...b.schema.issues],
|
|
1351
|
+
schemaDiffIssues: [...a.schema.schemaDiffIssues, ...b.schema.schemaDiffIssues],
|
|
1352
|
+
counts: {
|
|
1353
|
+
pass: a.schema.counts.pass + b.schema.counts.pass,
|
|
1354
|
+
warn: a.schema.counts.warn + b.schema.counts.warn,
|
|
1355
|
+
fail: a.schema.counts.fail + b.schema.counts.fail,
|
|
1356
|
+
totalNodes: a.schema.counts.totalNodes + b.schema.counts.totalNodes
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* The single per-namespace-paired relational verify shared by the migration
|
|
1363
|
+
* planner and the family schema verify — there is exactly one such operation.
|
|
1364
|
+
*
|
|
1365
|
+
* Each contract namespace is paired to the introspected namespace node holding
|
|
1366
|
+
* the same DDL schema, then `verifySqlSchema` checks that namespace's tables
|
|
1367
|
+
* against the matching actual node (a contract table under `auth` is only ever
|
|
1368
|
+
* looked up in the `auth` actual node, so a multi-schema database no longer
|
|
1369
|
+
* reports tables in other schemas as missing). The full contract is passed every
|
|
1370
|
+
* time — `restrictToNamespaceIds` scopes only which tables are checked, so
|
|
1371
|
+
* cross-namespace value-set / control-policy resolution is unaffected.
|
|
1372
|
+
*
|
|
1373
|
+
* The DDL schema of each contract namespace is read from a single-namespace
|
|
1374
|
+
* expected projection (`buildExpectedSchema`), which both callers already build
|
|
1375
|
+
* the same way. Empty contract namespaces verify nothing and are skipped.
|
|
1376
|
+
* Single-schema (one namespace) and SQLite's flat schema are one pairing —
|
|
1377
|
+
* byte-identical to the prior per-node verify.
|
|
1378
|
+
*/
|
|
1379
|
+
function verifySqlSchemaTree(options) {
|
|
1380
|
+
const baseOptions = {
|
|
1381
|
+
contract: options.contract,
|
|
1382
|
+
strict: options.strict,
|
|
1383
|
+
typeMetadataRegistry: options.typeMetadataRegistry,
|
|
1384
|
+
frameworkComponents: options.frameworkComponents,
|
|
1385
|
+
...ifDefined("normalizeDefault", options.normalizeDefault),
|
|
1386
|
+
...ifDefined("normalizeNativeType", options.normalizeNativeType)
|
|
1387
|
+
};
|
|
1388
|
+
const actualNodes = namespaceSchemaNodes(options.actualSchema);
|
|
1389
|
+
const actualByName = /* @__PURE__ */ new Map();
|
|
1390
|
+
for (const node of actualNodes) {
|
|
1391
|
+
const name = namespaceSchemaName(node);
|
|
1392
|
+
if (name !== void 0) actualByName.set(name, node);
|
|
1393
|
+
}
|
|
1394
|
+
const soleFlatActual = actualByName.size === 0 ? actualNodes[0] : void 0;
|
|
1395
|
+
const emptyNamespace = { tables: {} };
|
|
1396
|
+
let combined;
|
|
1397
|
+
for (const namespaceId of Object.keys(options.contract.storage.namespaces)) {
|
|
1398
|
+
const namespace = options.contract.storage.namespaces[namespaceId];
|
|
1399
|
+
if (!namespace || Object.keys(namespace.entries.table ?? {}).length === 0) continue;
|
|
1400
|
+
const ddlSchema = namespaceSchemaNodes(options.buildExpectedSchema(scopeContractToNamespace(options.contract, namespaceId))).map(namespaceSchemaName).find((name) => name !== void 0);
|
|
1401
|
+
const actualNode = (ddlSchema !== void 0 ? actualByName.get(ddlSchema) : soleFlatActual) ?? emptyNamespace;
|
|
1402
|
+
const result = verifySqlSchema({
|
|
1403
|
+
...baseOptions,
|
|
1404
|
+
schema: actualNode,
|
|
1405
|
+
restrictToNamespaceIds: new Set([namespaceId])
|
|
1406
|
+
});
|
|
1407
|
+
combined = combined === void 0 ? result : mergeVerifyResults(combined, result);
|
|
1408
|
+
}
|
|
1409
|
+
return combined ?? verifySqlSchema({
|
|
1410
|
+
...baseOptions,
|
|
1411
|
+
schema: emptyNamespace
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1498
1414
|
//#endregion
|
|
1499
|
-
export {
|
|
1415
|
+
export { arraysEqual, isIndexSatisfied, isUniqueConstraintSatisfied, verifySqlSchema, verifySqlSchemaTree };
|
|
1500
1416
|
|
|
1501
|
-
//# sourceMappingURL=
|
|
1417
|
+
//# sourceMappingURL=diff.mjs.map
|