@prisma-next/family-sql 0.12.0-dev.4 → 0.12.0-dev.41
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-adapter-uYnmu04p.d.mts +181 -0
- package/dist/control-adapter-uYnmu04p.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +2 -109
- package/dist/control.d.mts +118 -4
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +226 -40
- package/dist/control.mjs.map +1 -1
- package/dist/ir.d.mts +2 -2
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/runtime.d.mts +4 -2
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +3 -1
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema-verify.d.mts +2 -1
- package/dist/schema-verify.d.mts.map +1 -1
- package/dist/schema-verify.mjs +1 -1
- package/dist/{sql-contract-serializer-8axtK4lg.mjs → sql-contract-serializer-nNw1yk9P.mjs} +14 -35
- package/dist/sql-contract-serializer-nNw1yk9P.mjs.map +1 -0
- package/dist/{types-CeeCStqw.d.mts → types-DxNgxFkF.d.mts} +61 -7
- package/dist/types-DxNgxFkF.d.mts.map +1 -0
- package/dist/{verify-sql-schema-CN7pPoTC.d.mts → verify-sql-schema-Cj3c2t5Z.d.mts} +8 -2
- package/dist/verify-sql-schema-Cj3c2t5Z.d.mts.map +1 -0
- package/dist/{verify-sql-schema-CYLsGCFO.mjs → verify-sql-schema-DV_d2XCG.mjs} +410 -323
- package/dist/verify-sql-schema-DV_d2XCG.mjs.map +1 -0
- package/package.json +21 -21
- package/src/core/control-adapter.ts +112 -3
- package/src/core/control-instance.ts +143 -56
- package/src/core/default-namespace.ts +9 -0
- package/src/core/ir/sql-contract-serializer-base.ts +42 -57
- package/src/core/migrations/contract-to-schema-ir.ts +12 -8
- package/src/core/migrations/control-policy.ts +322 -0
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/plan-helpers.ts +16 -0
- package/src/core/migrations/types.ts +12 -2
- package/src/core/schema-verify/control-verify-emit.ts +46 -0
- package/src/core/schema-verify/verifier-disposition.ts +53 -0
- package/src/core/schema-verify/verify-helpers.ts +151 -110
- package/src/core/schema-verify/verify-sql-schema.ts +305 -178
- package/src/exports/control.ts +6 -0
- package/src/exports/runtime.ts +7 -0
- package/dist/control-adapter.d.mts.map +0 -1
- package/dist/sql-contract-serializer-8axtK4lg.mjs.map +0 -1
- package/dist/types-CeeCStqw.d.mts.map +0 -1
- package/dist/verify-sql-schema-CN7pPoTC.d.mts.map +0 -1
- package/dist/verify-sql-schema-CYLsGCFO.mjs.map +0 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { assertUniqueCodecOwner } from "@prisma-next/framework-components/control";
|
|
1
|
+
import { assertUniqueCodecOwner, dispositionForCategory } from "@prisma-next/framework-components/control";
|
|
2
2
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
3
3
|
import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
|
|
4
4
|
import { StorageTable, isPostgresEnumStorageEntry, isStorageTypeInstance } from "@prisma-next/sql-contract/types";
|
|
5
|
+
import { blindCast } from "@prisma-next/utils/casts";
|
|
6
|
+
import { effectiveControlPolicy } from "@prisma-next/contract/types";
|
|
5
7
|
import { canonicalStringify } from "@prisma-next/utils/canonical-stringify";
|
|
6
8
|
//#region src/core/assembly.ts
|
|
7
9
|
function hasCodecControlHooks(descriptor) {
|
|
@@ -31,6 +33,71 @@ function extractCodecControlHooks(descriptors) {
|
|
|
31
33
|
return hooks;
|
|
32
34
|
}
|
|
33
35
|
//#endregion
|
|
36
|
+
//#region src/core/schema-verify/verifier-disposition.ts
|
|
37
|
+
/**
|
|
38
|
+
* Classifies the relational verifier issue kinds the SQL family emits (tables,
|
|
39
|
+
* columns, constraints, indexes, defaults, enum types) into the target-neutral
|
|
40
|
+
* categories the framework grades. The relational vocabulary lives here, in the
|
|
41
|
+
* SQL domain — the framework never switches over `extra_foreign_key` and friends.
|
|
42
|
+
*/
|
|
43
|
+
function classifySqlVerifierIssueKind(kind) {
|
|
44
|
+
switch (kind) {
|
|
45
|
+
case "extra_column": return "extraNestedElement";
|
|
46
|
+
case "extra_primary_key":
|
|
47
|
+
case "extra_foreign_key":
|
|
48
|
+
case "extra_unique_constraint":
|
|
49
|
+
case "extra_index":
|
|
50
|
+
case "extra_validator":
|
|
51
|
+
case "extra_default": return "extraAuxiliary";
|
|
52
|
+
case "extra_table": return "extraTopLevelObject";
|
|
53
|
+
case "missing_schema":
|
|
54
|
+
case "missing_table":
|
|
55
|
+
case "missing_column":
|
|
56
|
+
case "type_missing":
|
|
57
|
+
case "default_missing": return "declaredMissing";
|
|
58
|
+
case "type_values_mismatch":
|
|
59
|
+
case "enum_values_changed": return "valueDrift";
|
|
60
|
+
case "type_mismatch":
|
|
61
|
+
case "nullability_mismatch":
|
|
62
|
+
case "primary_key_mismatch":
|
|
63
|
+
case "foreign_key_mismatch":
|
|
64
|
+
case "unique_constraint_mismatch":
|
|
65
|
+
case "index_mismatch":
|
|
66
|
+
case "default_mismatch": return "declaredIncompatible";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function verifierDisposition(controlPolicy, issueKind) {
|
|
70
|
+
return dispositionForCategory(controlPolicy, classifySqlVerifierIssueKind(issueKind));
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/core/schema-verify/control-verify-emit.ts
|
|
74
|
+
/**
|
|
75
|
+
* Grades `issue` under `controlPolicy` and, unless suppressed, pushes both the
|
|
76
|
+
* issue and a status-stamped verification node. Returns the resolved outcome so
|
|
77
|
+
* the caller never re-grades the same issue.
|
|
78
|
+
*/
|
|
79
|
+
function emitIssueAndNodeUnderControlPolicy(controlPolicy, issue, node, issues, nodes) {
|
|
80
|
+
const disposition = verifierDisposition(controlPolicy, issue.kind);
|
|
81
|
+
if (disposition === "suppress") return disposition;
|
|
82
|
+
issues.push(issue);
|
|
83
|
+
nodes.push({
|
|
84
|
+
...node,
|
|
85
|
+
status: disposition
|
|
86
|
+
});
|
|
87
|
+
return disposition;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Grades `issue` under `controlPolicy` and, unless suppressed, pushes the issue
|
|
91
|
+
* (no verification node). Returns the resolved outcome so the caller maps it to
|
|
92
|
+
* a node status itself without re-grading.
|
|
93
|
+
*/
|
|
94
|
+
function emitIssueUnderControlPolicy(controlPolicy, issue, issues) {
|
|
95
|
+
const disposition = verifierDisposition(controlPolicy, issue.kind);
|
|
96
|
+
if (disposition === "suppress") return disposition;
|
|
97
|
+
issues.push(issue);
|
|
98
|
+
return disposition;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
34
101
|
//#region src/core/schema-verify/verify-helpers.ts
|
|
35
102
|
function indexOptionsLooselyEqual(a, b) {
|
|
36
103
|
const aKeys = a ? Object.keys(a).sort() : [];
|
|
@@ -92,27 +159,27 @@ function isIndexSatisfied(indexes, uniques, columns) {
|
|
|
92
159
|
* Uses semantic satisfaction: identity is based on (table + kind + columns).
|
|
93
160
|
* Name differences are ignored by default (names are for DDL/diagnostics, not identity).
|
|
94
161
|
*/
|
|
95
|
-
function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, issues) {
|
|
162
|
+
function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, tableControlPolicy, issues) {
|
|
96
163
|
if (!schemaPK) {
|
|
97
|
-
|
|
164
|
+
const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
|
|
98
165
|
kind: "primary_key_mismatch",
|
|
99
166
|
table: tableName,
|
|
100
167
|
namespaceId,
|
|
101
168
|
expected: contractPK.columns.join(", "),
|
|
102
169
|
message: `Table "${tableName}" is missing primary key`
|
|
103
|
-
});
|
|
104
|
-
return "
|
|
170
|
+
}, issues);
|
|
171
|
+
return outcome === "suppress" ? "pass" : outcome;
|
|
105
172
|
}
|
|
106
173
|
if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
|
|
107
|
-
|
|
174
|
+
const outcome = emitIssueUnderControlPolicy(tableControlPolicy, {
|
|
108
175
|
kind: "primary_key_mismatch",
|
|
109
176
|
table: tableName,
|
|
110
177
|
namespaceId,
|
|
111
178
|
expected: contractPK.columns.join(", "),
|
|
112
179
|
actual: schemaPK.columns.join(", "),
|
|
113
180
|
message: `Table "${tableName}" has primary key mismatch: expected columns [${contractPK.columns.join(", ")}], got [${schemaPK.columns.join(", ")}]`
|
|
114
|
-
});
|
|
115
|
-
return "
|
|
181
|
+
}, issues);
|
|
182
|
+
return outcome === "suppress" ? "pass" : outcome;
|
|
116
183
|
}
|
|
117
184
|
return "pass";
|
|
118
185
|
}
|
|
@@ -123,7 +190,7 @@ function verifyPrimaryKey(contractPK, schemaPK, tableName, namespaceId, issues)
|
|
|
123
190
|
* Uses semantic satisfaction: identity is based on (table + columns + referenced table + referenced columns).
|
|
124
191
|
* Name differences are ignored by default (names are for DDL/diagnostics, not identity).
|
|
125
192
|
*/
|
|
126
|
-
function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, tablePath, issues, strict) {
|
|
193
|
+
function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
|
|
127
194
|
const nodes = [];
|
|
128
195
|
for (const contractFK of contractFKs) {
|
|
129
196
|
const fkPath = `${tablePath}.foreignKeys[${contractFK.source.columns.join(",")}]`;
|
|
@@ -131,32 +198,30 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
|
|
|
131
198
|
const tablesMatch = fk.referencedSchema !== void 0 && contractFK.target.namespaceId !== UNBOUND_NAMESPACE_ID ? fk.referencedSchema === contractFK.target.namespaceId && fk.referencedTable === contractFK.target.tableName : fk.referencedTable === contractFK.target.tableName;
|
|
132
199
|
return arraysEqual(fk.columns, contractFK.source.columns) && tablesMatch && arraysEqual(fk.referencedColumns, contractFK.target.columns);
|
|
133
200
|
});
|
|
134
|
-
if (!matchingFK) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
});
|
|
153
|
-
} else {
|
|
201
|
+
if (!matchingFK) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
202
|
+
kind: "foreign_key_mismatch",
|
|
203
|
+
table: tableName,
|
|
204
|
+
namespaceId,
|
|
205
|
+
expected: `${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`,
|
|
206
|
+
message: `Table "${tableName}" is missing foreign key: ${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(", ")})`
|
|
207
|
+
}, {
|
|
208
|
+
status: "fail",
|
|
209
|
+
kind: "foreignKey",
|
|
210
|
+
name: `foreignKey(${contractFK.source.columns.join(", ")})`,
|
|
211
|
+
contractPath: fkPath,
|
|
212
|
+
code: "foreign_key_mismatch",
|
|
213
|
+
message: "Foreign key missing",
|
|
214
|
+
expected: contractFK,
|
|
215
|
+
actual: void 0,
|
|
216
|
+
children: []
|
|
217
|
+
}, issues, nodes);
|
|
218
|
+
else {
|
|
154
219
|
const actionMismatches = getReferentialActionMismatches(contractFK, matchingFK);
|
|
155
220
|
if (actionMismatches.length > 0) {
|
|
156
221
|
const combinedMessage = actionMismatches.map((m) => m.message).join("; ");
|
|
157
222
|
const combinedExpected = actionMismatches.map((m) => m.expected).join(", ");
|
|
158
223
|
const combinedActual = actionMismatches.map((m) => m.actual).join(", ");
|
|
159
|
-
|
|
224
|
+
emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
160
225
|
kind: "foreign_key_mismatch",
|
|
161
226
|
table: tableName,
|
|
162
227
|
namespaceId,
|
|
@@ -164,8 +229,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
|
|
|
164
229
|
expected: combinedExpected,
|
|
165
230
|
actual: combinedActual,
|
|
166
231
|
message: `Table "${tableName}" foreign key ${contractFK.source.columns.join(", ")} -> ${contractFK.target.tableName}: ${combinedMessage}`
|
|
167
|
-
}
|
|
168
|
-
nodes.push({
|
|
232
|
+
}, {
|
|
169
233
|
status: "fail",
|
|
170
234
|
kind: "foreignKey",
|
|
171
235
|
name: `foreignKey(${contractFK.source.columns.join(", ")})`,
|
|
@@ -175,7 +239,7 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
|
|
|
175
239
|
expected: contractFK,
|
|
176
240
|
actual: matchingFK,
|
|
177
241
|
children: []
|
|
178
|
-
});
|
|
242
|
+
}, issues, nodes);
|
|
179
243
|
} else nodes.push({
|
|
180
244
|
status: "pass",
|
|
181
245
|
kind: "foreignKey",
|
|
@@ -193,26 +257,23 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
|
|
|
193
257
|
for (const schemaFK of schemaFKs) if (!contractFKs.find((fk) => {
|
|
194
258
|
const tablesMatch = schemaFK.referencedSchema !== void 0 && fk.target.namespaceId !== UNBOUND_NAMESPACE_ID ? schemaFK.referencedSchema === fk.target.namespaceId && schemaFK.referencedTable === fk.target.tableName : schemaFK.referencedTable === fk.target.tableName;
|
|
195
259
|
return arraysEqual(fk.source.columns, schemaFK.columns) && tablesMatch && arraysEqual(fk.target.columns, schemaFK.referencedColumns);
|
|
196
|
-
})) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
children: []
|
|
214
|
-
});
|
|
215
|
-
}
|
|
260
|
+
})) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
261
|
+
kind: "extra_foreign_key",
|
|
262
|
+
table: tableName,
|
|
263
|
+
namespaceId,
|
|
264
|
+
indexOrConstraint: schemaFK.name ?? `fk(${schemaFK.columns.join(",")})`,
|
|
265
|
+
message: `Extra foreign key found in database (not in contract): ${schemaFK.columns.join(", ")} -> ${schemaFK.referencedTable}(${schemaFK.referencedColumns.join(", ")})`
|
|
266
|
+
}, {
|
|
267
|
+
status: "fail",
|
|
268
|
+
kind: "foreignKey",
|
|
269
|
+
name: `foreignKey(${schemaFK.columns.join(", ")})`,
|
|
270
|
+
contractPath: `${tablePath}.foreignKeys[${schemaFK.columns.join(",")}]`,
|
|
271
|
+
code: "extra_foreign_key",
|
|
272
|
+
message: "Extra foreign key found",
|
|
273
|
+
expected: void 0,
|
|
274
|
+
actual: schemaFK,
|
|
275
|
+
children: []
|
|
276
|
+
}, issues, nodes);
|
|
216
277
|
}
|
|
217
278
|
return nodes;
|
|
218
279
|
}
|
|
@@ -227,32 +288,30 @@ function verifyForeignKeys(contractFKs, schemaFKs, tableName, namespaceId, table
|
|
|
227
288
|
*
|
|
228
289
|
* Name differences are ignored by default (names are for DDL/diagnostics, not identity).
|
|
229
290
|
*/
|
|
230
|
-
function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes, tableName, namespaceId, tablePath, issues, strict) {
|
|
291
|
+
function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
|
|
231
292
|
const nodes = [];
|
|
232
293
|
for (const contractUnique of contractUniques) {
|
|
233
294
|
const uniquePath = `${tablePath}.uniques[${contractUnique.columns.join(",")}]`;
|
|
234
295
|
const matchingUnique = schemaUniques.find((u) => arraysEqual(u.columns, contractUnique.columns));
|
|
235
296
|
const matchingUniqueIndex = !matchingUnique && schemaIndexes.find((idx) => idx.unique && arraysEqual(idx.columns, contractUnique.columns));
|
|
236
|
-
if (!matchingUnique && !matchingUniqueIndex) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
});
|
|
255
|
-
} else nodes.push({
|
|
297
|
+
if (!matchingUnique && !matchingUniqueIndex) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
298
|
+
kind: "unique_constraint_mismatch",
|
|
299
|
+
table: tableName,
|
|
300
|
+
namespaceId,
|
|
301
|
+
expected: contractUnique.columns.join(", "),
|
|
302
|
+
message: `Table "${tableName}" is missing unique constraint: ${contractUnique.columns.join(", ")}`
|
|
303
|
+
}, {
|
|
304
|
+
status: "fail",
|
|
305
|
+
kind: "unique",
|
|
306
|
+
name: `unique(${contractUnique.columns.join(", ")})`,
|
|
307
|
+
contractPath: uniquePath,
|
|
308
|
+
code: "unique_constraint_mismatch",
|
|
309
|
+
message: "Unique constraint missing",
|
|
310
|
+
expected: contractUnique,
|
|
311
|
+
actual: void 0,
|
|
312
|
+
children: []
|
|
313
|
+
}, issues, nodes);
|
|
314
|
+
else nodes.push({
|
|
256
315
|
status: "pass",
|
|
257
316
|
kind: "unique",
|
|
258
317
|
name: `unique(${contractUnique.columns.join(", ")})`,
|
|
@@ -265,26 +324,23 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
|
|
|
265
324
|
});
|
|
266
325
|
}
|
|
267
326
|
if (strict) {
|
|
268
|
-
for (const schemaUnique of schemaUniques) if (!contractUniques.find((u) => arraysEqual(u.columns, schemaUnique.columns))) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
children: []
|
|
286
|
-
});
|
|
287
|
-
}
|
|
327
|
+
for (const schemaUnique of schemaUniques) if (!contractUniques.find((u) => arraysEqual(u.columns, schemaUnique.columns))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
328
|
+
kind: "extra_unique_constraint",
|
|
329
|
+
table: tableName,
|
|
330
|
+
namespaceId,
|
|
331
|
+
indexOrConstraint: schemaUnique.name ?? `unique(${schemaUnique.columns.join(",")})`,
|
|
332
|
+
message: `Extra unique constraint found in database (not in contract): ${schemaUnique.columns.join(", ")}`
|
|
333
|
+
}, {
|
|
334
|
+
status: "fail",
|
|
335
|
+
kind: "unique",
|
|
336
|
+
name: `unique(${schemaUnique.columns.join(", ")})`,
|
|
337
|
+
contractPath: `${tablePath}.uniques[${schemaUnique.columns.join(",")}]`,
|
|
338
|
+
code: "extra_unique_constraint",
|
|
339
|
+
message: "Extra unique constraint found",
|
|
340
|
+
expected: void 0,
|
|
341
|
+
actual: schemaUnique,
|
|
342
|
+
children: []
|
|
343
|
+
}, issues, nodes);
|
|
288
344
|
}
|
|
289
345
|
return nodes;
|
|
290
346
|
}
|
|
@@ -299,32 +355,30 @@ function verifyUniqueConstraints(contractUniques, schemaUniques, schemaIndexes,
|
|
|
299
355
|
*
|
|
300
356
|
* Name differences are ignored by default (names are for DDL/diagnostics, not identity).
|
|
301
357
|
*/
|
|
302
|
-
function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName, namespaceId, tablePath, issues, strict) {
|
|
358
|
+
function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict) {
|
|
303
359
|
const nodes = [];
|
|
304
360
|
for (const contractIndex of contractIndexes) {
|
|
305
361
|
const indexPath = `${tablePath}.indexes[${contractIndex.columns.join(",")}]`;
|
|
306
362
|
const matchingIndex = schemaIndexes.find((idx) => arraysEqual(idx.columns, contractIndex.columns) && indexExtrasMatch(contractIndex, idx));
|
|
307
363
|
const matchingUniqueConstraint = !matchingIndex && contractIndex.type === void 0 && contractIndex.options === void 0 && schemaUniques.find((u) => arraysEqual(u.columns, contractIndex.columns));
|
|
308
|
-
if (!matchingIndex && !matchingUniqueConstraint) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
});
|
|
327
|
-
} else nodes.push({
|
|
364
|
+
if (!matchingIndex && !matchingUniqueConstraint) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
365
|
+
kind: "index_mismatch",
|
|
366
|
+
table: tableName,
|
|
367
|
+
namespaceId,
|
|
368
|
+
expected: contractIndex.columns.join(", "),
|
|
369
|
+
message: `Table "${tableName}" is missing index: ${contractIndex.columns.join(", ")}`
|
|
370
|
+
}, {
|
|
371
|
+
status: "fail",
|
|
372
|
+
kind: "index",
|
|
373
|
+
name: `index(${contractIndex.columns.join(", ")})`,
|
|
374
|
+
contractPath: indexPath,
|
|
375
|
+
code: "index_mismatch",
|
|
376
|
+
message: "Index missing",
|
|
377
|
+
expected: contractIndex,
|
|
378
|
+
actual: void 0,
|
|
379
|
+
children: []
|
|
380
|
+
}, issues, nodes);
|
|
381
|
+
else nodes.push({
|
|
328
382
|
status: "pass",
|
|
329
383
|
kind: "index",
|
|
330
384
|
name: `index(${contractIndex.columns.join(", ")})`,
|
|
@@ -338,26 +392,23 @@ function verifyIndexes(contractIndexes, schemaIndexes, schemaUniques, tableName,
|
|
|
338
392
|
}
|
|
339
393
|
if (strict) for (const schemaIndex of schemaIndexes) {
|
|
340
394
|
if (schemaIndex.unique) continue;
|
|
341
|
-
if (!contractIndexes.find((idx) => arraysEqual(idx.columns, schemaIndex.columns) && indexExtrasMatch(idx, schemaIndex))) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
children: []
|
|
359
|
-
});
|
|
360
|
-
}
|
|
395
|
+
if (!contractIndexes.find((idx) => arraysEqual(idx.columns, schemaIndex.columns) && indexExtrasMatch(idx, schemaIndex))) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
396
|
+
kind: "extra_index",
|
|
397
|
+
table: tableName,
|
|
398
|
+
namespaceId,
|
|
399
|
+
indexOrConstraint: schemaIndex.name ?? `idx(${schemaIndex.columns.join(",")})`,
|
|
400
|
+
message: `Extra index found in database (not in contract): ${schemaIndex.columns.join(", ")}`
|
|
401
|
+
}, {
|
|
402
|
+
status: "fail",
|
|
403
|
+
kind: "index",
|
|
404
|
+
name: `index(${schemaIndex.columns.join(", ")})`,
|
|
405
|
+
contractPath: `${tablePath}.indexes[${schemaIndex.columns.join(",")}]`,
|
|
406
|
+
code: "extra_index",
|
|
407
|
+
message: "Extra index found",
|
|
408
|
+
expected: void 0,
|
|
409
|
+
actual: schemaIndex,
|
|
410
|
+
children: []
|
|
411
|
+
}, issues, nodes);
|
|
361
412
|
}
|
|
362
413
|
return nodes;
|
|
363
414
|
}
|
|
@@ -429,13 +480,13 @@ function normalizeReferentialAction(action) {
|
|
|
429
480
|
* @returns VerifyDatabaseSchemaResult with verification tree and issues
|
|
430
481
|
*/
|
|
431
482
|
function verifySqlSchema(options) {
|
|
432
|
-
const { contract, schema, strict, context, typeMetadataRegistry, normalizeDefault, normalizeNativeType, resolveExistingEnumValues } = options;
|
|
483
|
+
const { contract, schema, strict, context, typeMetadataRegistry, normalizeDefault, normalizeNativeType, resolveExistingEnumValues, columnsCompatible } = options;
|
|
433
484
|
const startTime = Date.now();
|
|
434
485
|
const codecHooks = extractCodecControlHooks(options.frameworkComponents);
|
|
435
486
|
const { contractStorageHash, contractProfileHash, contractTarget } = extractContractMetadata(contract);
|
|
436
487
|
const allStorageTypesMap = { ...contract.storage.types ?? {} };
|
|
437
488
|
for (const ns of Object.values(contract.storage.namespaces)) {
|
|
438
|
-
const nsEnums = ns.
|
|
489
|
+
const nsEnums = blindCast(ns.entries).type;
|
|
439
490
|
if (nsEnums) for (const [k, v] of Object.entries(nsEnums)) allStorageTypesMap[k] = v;
|
|
440
491
|
}
|
|
441
492
|
const { issues, rootChildren } = verifySchemaTables({
|
|
@@ -446,57 +497,62 @@ function verifySqlSchema(options) {
|
|
|
446
497
|
codecHooks,
|
|
447
498
|
storageTypes: allStorageTypesMap,
|
|
448
499
|
...ifDefined("normalizeDefault", normalizeDefault),
|
|
449
|
-
...ifDefined("normalizeNativeType", normalizeNativeType)
|
|
500
|
+
...ifDefined("normalizeNativeType", normalizeNativeType),
|
|
501
|
+
...ifDefined("columnsCompatible", columnsCompatible)
|
|
450
502
|
});
|
|
451
503
|
validateFrameworkComponentsForExtensions(contract, options.frameworkComponents);
|
|
452
504
|
const typeNodes = [];
|
|
453
|
-
const pushTypeNode = (typeName, contractPath, typeIssues) => {
|
|
454
|
-
|
|
505
|
+
const pushTypeNode = (typeName, contractPath, typeIssues, controlPolicy) => {
|
|
506
|
+
let status = "pass";
|
|
507
|
+
let code = "";
|
|
508
|
+
let emitted = 0;
|
|
509
|
+
for (const issue of typeIssues) {
|
|
510
|
+
const disposition = verifierDisposition(controlPolicy, issue.kind);
|
|
511
|
+
if (disposition === "suppress") continue;
|
|
512
|
+
issues.push(issue);
|
|
513
|
+
emitted += 1;
|
|
514
|
+
if (code === "") code = issue.kind;
|
|
515
|
+
if (disposition === "fail") status = "fail";
|
|
516
|
+
else if (disposition === "warn" && status !== "fail") status = "warn";
|
|
517
|
+
}
|
|
455
518
|
typeNodes.push({
|
|
456
|
-
status
|
|
519
|
+
status,
|
|
457
520
|
kind: "storageType",
|
|
458
521
|
name: `type ${typeName}`,
|
|
459
522
|
contractPath,
|
|
460
|
-
code:
|
|
461
|
-
message:
|
|
523
|
+
code: status === "pass" ? "" : code,
|
|
524
|
+
message: emitted > 0 ? `${emitted} issue${emitted === 1 ? "" : "s"}` : "",
|
|
462
525
|
expected: void 0,
|
|
463
526
|
actual: void 0,
|
|
464
527
|
children: []
|
|
465
528
|
});
|
|
466
529
|
};
|
|
467
|
-
for (const [typeName, typeInstance] of Object.entries(contract.storage.types ?? {})) if (
|
|
468
|
-
typeName,
|
|
469
|
-
typeInstance,
|
|
470
|
-
schema,
|
|
471
|
-
resolveExistingEnumValues,
|
|
472
|
-
namespaceId: UNBOUND_NAMESPACE_ID
|
|
473
|
-
}));
|
|
474
|
-
else if (isStorageTypeInstance(typeInstance)) {
|
|
530
|
+
for (const [typeName, typeInstance] of Object.entries(contract.storage.types ?? {})) if (isStorageTypeInstance(typeInstance)) {
|
|
475
531
|
const hook = codecHooks.get(typeInstance.codecId);
|
|
476
532
|
pushTypeNode(typeName, `storage.types.${typeName}`, hook?.verifyType ? hook.verifyType({
|
|
477
533
|
typeName,
|
|
478
534
|
typeInstance,
|
|
479
535
|
schema
|
|
480
|
-
}) : []);
|
|
536
|
+
}) : [], effectiveControlPolicy(void 0, contract.defaultControlPolicy));
|
|
481
537
|
}
|
|
482
538
|
for (const nsId of Object.keys(contract.storage.namespaces)) {
|
|
483
539
|
const ns = contract.storage.namespaces[nsId];
|
|
484
540
|
if (!ns) continue;
|
|
485
|
-
const nsEnums = ns.
|
|
541
|
+
const nsEnums = ns.entries["type"];
|
|
486
542
|
if (!nsEnums) continue;
|
|
487
543
|
for (const [typeName, entry] of Object.entries(nsEnums)) {
|
|
488
544
|
if (!isPostgresEnumStorageEntry(entry)) continue;
|
|
489
|
-
pushTypeNode(typeName, `storage.namespaces.${nsId}.
|
|
545
|
+
pushTypeNode(typeName, `storage.namespaces.${nsId}.entries.type.${typeName}`, verifyEnumType({
|
|
490
546
|
typeName,
|
|
491
547
|
typeInstance: entry,
|
|
492
548
|
schema,
|
|
493
549
|
resolveExistingEnumValues,
|
|
494
550
|
namespaceId: nsId
|
|
495
|
-
}));
|
|
551
|
+
}), effectiveControlPolicy(entry.control, contract.defaultControlPolicy));
|
|
496
552
|
}
|
|
497
553
|
}
|
|
498
554
|
if (typeNodes.length > 0) {
|
|
499
|
-
const typesStatus = typeNodes.some((n) => n.status === "fail") ? "fail" : "pass";
|
|
555
|
+
const typesStatus = typeNodes.some((n) => n.status === "fail") ? "fail" : typeNodes.some((n) => n.status === "warn") ? "warn" : "pass";
|
|
500
556
|
rootChildren.push({
|
|
501
557
|
status: typesStatus,
|
|
502
558
|
kind: "storageTypes",
|
|
@@ -580,7 +636,8 @@ function extractContractMetadata(contract) {
|
|
|
580
636
|
};
|
|
581
637
|
}
|
|
582
638
|
function verifySchemaTables(options) {
|
|
583
|
-
const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
639
|
+
const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, columnsCompatible } = options;
|
|
640
|
+
const contractDefaultControl = contract.defaultControlPolicy;
|
|
584
641
|
const issues = [];
|
|
585
642
|
const rootChildren = [];
|
|
586
643
|
const schemaTables = schema.tables;
|
|
@@ -588,19 +645,19 @@ function verifySchemaTables(options) {
|
|
|
588
645
|
for (const namespaceId of namespaceIds) {
|
|
589
646
|
const ns = contract.storage.namespaces[namespaceId];
|
|
590
647
|
if (!ns) continue;
|
|
591
|
-
for (const [tableName, contractTableRaw] of Object.entries(ns.
|
|
592
|
-
if (!(contractTableRaw instanceof StorageTable)) throw new Error(`verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.
|
|
648
|
+
for (const [tableName, contractTableRaw] of Object.entries(ns.entries.table)) {
|
|
649
|
+
if (!(contractTableRaw instanceof StorageTable)) throw new Error(`verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.entries.table.${tableName}`);
|
|
593
650
|
const contractTable = contractTableRaw;
|
|
651
|
+
const tableControlPolicy = effectiveControlPolicy(contractTable.control, contractDefaultControl);
|
|
594
652
|
const schemaTable = schemaTables[tableName];
|
|
595
|
-
const tablePath = `storage.namespaces.${namespaceId}.
|
|
653
|
+
const tablePath = `storage.namespaces.${namespaceId}.entries.table.${tableName}`;
|
|
596
654
|
if (!schemaTable) {
|
|
597
|
-
|
|
655
|
+
emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
598
656
|
kind: "missing_table",
|
|
599
657
|
table: tableName,
|
|
600
658
|
namespaceId,
|
|
601
659
|
message: `Table "${tableName}" is missing from database`
|
|
602
|
-
}
|
|
603
|
-
rootChildren.push({
|
|
660
|
+
}, {
|
|
604
661
|
status: "fail",
|
|
605
662
|
kind: "table",
|
|
606
663
|
name: `table ${tableName}`,
|
|
@@ -610,7 +667,7 @@ function verifySchemaTables(options) {
|
|
|
610
667
|
expected: void 0,
|
|
611
668
|
actual: void 0,
|
|
612
669
|
children: []
|
|
613
|
-
});
|
|
670
|
+
}, issues, rootChildren);
|
|
614
671
|
continue;
|
|
615
672
|
}
|
|
616
673
|
const tableChildren = verifyTableChildren({
|
|
@@ -619,36 +676,35 @@ function verifySchemaTables(options) {
|
|
|
619
676
|
tableName,
|
|
620
677
|
namespaceId,
|
|
621
678
|
tablePath,
|
|
679
|
+
tableControlPolicy,
|
|
622
680
|
issues,
|
|
623
681
|
strict,
|
|
624
682
|
typeMetadataRegistry,
|
|
625
683
|
codecHooks,
|
|
626
684
|
storageTypes,
|
|
627
685
|
...ifDefined("normalizeDefault", normalizeDefault),
|
|
628
|
-
...ifDefined("normalizeNativeType", normalizeNativeType)
|
|
686
|
+
...ifDefined("normalizeNativeType", normalizeNativeType),
|
|
687
|
+
...ifDefined("columnsCompatible", columnsCompatible)
|
|
629
688
|
});
|
|
630
689
|
rootChildren.push(buildTableNode(tableName, tablePath, tableChildren));
|
|
631
690
|
}
|
|
632
691
|
}
|
|
633
692
|
if (strict) {
|
|
634
|
-
for (const tableName of Object.keys(schemaTables)) if (!namespaceIds.some((namespaceId) => contract.storage.namespaces[namespaceId]?.
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
children: []
|
|
650
|
-
});
|
|
651
|
-
}
|
|
693
|
+
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), {
|
|
694
|
+
kind: "extra_table",
|
|
695
|
+
table: tableName,
|
|
696
|
+
message: `Extra table "${tableName}" found in database (not in contract)`
|
|
697
|
+
}, {
|
|
698
|
+
status: "fail",
|
|
699
|
+
kind: "table",
|
|
700
|
+
name: `table ${tableName}`,
|
|
701
|
+
contractPath: `storage.namespaces.*.entries.table.${tableName}`,
|
|
702
|
+
code: "extra_table",
|
|
703
|
+
message: `Extra table "${tableName}" found`,
|
|
704
|
+
expected: void 0,
|
|
705
|
+
actual: void 0,
|
|
706
|
+
children: []
|
|
707
|
+
}, issues, rootChildren);
|
|
652
708
|
}
|
|
653
709
|
return {
|
|
654
710
|
issues,
|
|
@@ -656,7 +712,7 @@ function verifySchemaTables(options) {
|
|
|
656
712
|
};
|
|
657
713
|
}
|
|
658
714
|
function verifyTableChildren(options) {
|
|
659
|
-
const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
715
|
+
const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, columnsCompatible } = options;
|
|
660
716
|
const tableChildren = [];
|
|
661
717
|
const columnNodes = collectContractColumnNodes({
|
|
662
718
|
contractTable,
|
|
@@ -664,13 +720,15 @@ function verifyTableChildren(options) {
|
|
|
664
720
|
tableName,
|
|
665
721
|
namespaceId,
|
|
666
722
|
tablePath,
|
|
723
|
+
tableControlPolicy,
|
|
667
724
|
issues,
|
|
668
725
|
strict,
|
|
669
726
|
typeMetadataRegistry,
|
|
670
727
|
codecHooks,
|
|
671
728
|
storageTypes,
|
|
672
729
|
...ifDefined("normalizeDefault", normalizeDefault),
|
|
673
|
-
...ifDefined("normalizeNativeType", normalizeNativeType)
|
|
730
|
+
...ifDefined("normalizeNativeType", normalizeNativeType),
|
|
731
|
+
...ifDefined("columnsCompatible", columnsCompatible)
|
|
674
732
|
});
|
|
675
733
|
if (columnNodes.length > 0) tableChildren.push(buildColumnsNode(tablePath, columnNodes));
|
|
676
734
|
if (strict) appendExtraColumnNodes({
|
|
@@ -679,77 +737,87 @@ function verifyTableChildren(options) {
|
|
|
679
737
|
tableName,
|
|
680
738
|
namespaceId,
|
|
681
739
|
tablePath,
|
|
740
|
+
tableControlPolicy,
|
|
682
741
|
issues,
|
|
683
742
|
columnNodes
|
|
684
743
|
});
|
|
685
|
-
if (contractTable.primaryKey)
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
689
|
-
contractPath: `${tablePath}.primaryKey`,
|
|
690
|
-
code: "primary_key_mismatch",
|
|
691
|
-
message: "Primary key mismatch",
|
|
692
|
-
expected: contractTable.primaryKey,
|
|
693
|
-
actual: schemaTable.primaryKey,
|
|
694
|
-
children: []
|
|
695
|
-
});
|
|
696
|
-
else tableChildren.push({
|
|
697
|
-
status: "pass",
|
|
698
|
-
kind: "primaryKey",
|
|
699
|
-
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
700
|
-
contractPath: `${tablePath}.primaryKey`,
|
|
701
|
-
code: "",
|
|
702
|
-
message: "",
|
|
703
|
-
expected: void 0,
|
|
704
|
-
actual: void 0,
|
|
705
|
-
children: []
|
|
706
|
-
});
|
|
707
|
-
else if (schemaTable.primaryKey && strict) {
|
|
708
|
-
issues.push({
|
|
709
|
-
kind: "extra_primary_key",
|
|
710
|
-
table: tableName,
|
|
711
|
-
namespaceId,
|
|
712
|
-
message: "Extra primary key found in database (not in contract)"
|
|
713
|
-
});
|
|
714
|
-
tableChildren.push({
|
|
744
|
+
if (contractTable.primaryKey) {
|
|
745
|
+
const pkStatus = verifyPrimaryKey(contractTable.primaryKey, schemaTable.primaryKey, tableName, namespaceId, tableControlPolicy, issues);
|
|
746
|
+
if (pkStatus === "fail") tableChildren.push({
|
|
715
747
|
status: "fail",
|
|
716
748
|
kind: "primaryKey",
|
|
717
|
-
name: `primary key: ${
|
|
749
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
718
750
|
contractPath: `${tablePath}.primaryKey`,
|
|
719
|
-
code: "
|
|
720
|
-
message: "
|
|
721
|
-
expected:
|
|
751
|
+
code: "primary_key_mismatch",
|
|
752
|
+
message: "Primary key mismatch",
|
|
753
|
+
expected: contractTable.primaryKey,
|
|
722
754
|
actual: schemaTable.primaryKey,
|
|
723
755
|
children: []
|
|
724
756
|
});
|
|
725
|
-
|
|
757
|
+
else if (pkStatus === "warn") tableChildren.push({
|
|
758
|
+
status: "warn",
|
|
759
|
+
kind: "primaryKey",
|
|
760
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
761
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
762
|
+
code: "primary_key_mismatch",
|
|
763
|
+
message: "Primary key mismatch",
|
|
764
|
+
expected: contractTable.primaryKey,
|
|
765
|
+
actual: schemaTable.primaryKey,
|
|
766
|
+
children: []
|
|
767
|
+
});
|
|
768
|
+
else tableChildren.push({
|
|
769
|
+
status: "pass",
|
|
770
|
+
kind: "primaryKey",
|
|
771
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
772
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
773
|
+
code: "",
|
|
774
|
+
message: "",
|
|
775
|
+
expected: void 0,
|
|
776
|
+
actual: void 0,
|
|
777
|
+
children: []
|
|
778
|
+
});
|
|
779
|
+
} else if (schemaTable.primaryKey && strict) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
780
|
+
kind: "extra_primary_key",
|
|
781
|
+
table: tableName,
|
|
782
|
+
namespaceId,
|
|
783
|
+
message: "Extra primary key found in database (not in contract)"
|
|
784
|
+
}, {
|
|
785
|
+
status: "fail",
|
|
786
|
+
kind: "primaryKey",
|
|
787
|
+
name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
|
|
788
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
789
|
+
code: "extra_primary_key",
|
|
790
|
+
message: "Extra primary key found",
|
|
791
|
+
expected: void 0,
|
|
792
|
+
actual: schemaTable.primaryKey,
|
|
793
|
+
children: []
|
|
794
|
+
}, issues, tableChildren);
|
|
726
795
|
const constraintFks = contractTable.foreignKeys.filter((fk) => fk.constraint === true);
|
|
727
796
|
if (constraintFks.length > 0 || strict) {
|
|
728
|
-
const fkStatuses = verifyForeignKeys(constraintFks, schemaTable.foreignKeys, tableName, namespaceId, tablePath, issues, strict);
|
|
797
|
+
const fkStatuses = verifyForeignKeys(constraintFks, schemaTable.foreignKeys, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
|
|
729
798
|
tableChildren.push(...fkStatuses);
|
|
730
799
|
}
|
|
731
|
-
const uniqueStatuses = verifyUniqueConstraints(contractTable.uniques, schemaTable.uniques, schemaTable.indexes, tableName, namespaceId, tablePath, issues, strict);
|
|
800
|
+
const uniqueStatuses = verifyUniqueConstraints(contractTable.uniques, schemaTable.uniques, schemaTable.indexes, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
|
|
732
801
|
tableChildren.push(...uniqueStatuses);
|
|
733
802
|
const fkBackingIndexes = contractTable.foreignKeys.filter((fk) => fk.index === true && !contractTable.indexes.some((idx) => arraysEqual(idx.columns, fk.source.columns))).map((fk) => ({ columns: fk.source.columns }));
|
|
734
|
-
const indexStatuses = verifyIndexes([...contractTable.indexes, ...fkBackingIndexes], schemaTable.indexes, schemaTable.uniques, tableName, namespaceId, tablePath, issues, strict);
|
|
803
|
+
const indexStatuses = verifyIndexes([...contractTable.indexes, ...fkBackingIndexes], schemaTable.indexes, schemaTable.uniques, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
|
|
735
804
|
tableChildren.push(...indexStatuses);
|
|
736
805
|
return tableChildren;
|
|
737
806
|
}
|
|
738
807
|
function collectContractColumnNodes(options) {
|
|
739
|
-
const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
808
|
+
const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, columnsCompatible } = options;
|
|
740
809
|
const columnNodes = [];
|
|
741
810
|
for (const [columnName, contractColumn] of Object.entries(contractTable.columns)) {
|
|
742
811
|
const schemaColumn = schemaTable.columns[columnName];
|
|
743
812
|
const columnPath = `${tablePath}.columns.${columnName}`;
|
|
744
813
|
if (!schemaColumn) {
|
|
745
|
-
|
|
814
|
+
emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
746
815
|
kind: "missing_column",
|
|
747
816
|
table: tableName,
|
|
748
817
|
namespaceId,
|
|
749
818
|
column: columnName,
|
|
750
819
|
message: `Column "${tableName}"."${columnName}" is missing from database`
|
|
751
|
-
}
|
|
752
|
-
columnNodes.push({
|
|
820
|
+
}, {
|
|
753
821
|
status: "fail",
|
|
754
822
|
kind: "column",
|
|
755
823
|
name: `${columnName}: missing`,
|
|
@@ -759,7 +827,7 @@ function collectContractColumnNodes(options) {
|
|
|
759
827
|
expected: void 0,
|
|
760
828
|
actual: void 0,
|
|
761
829
|
children: []
|
|
762
|
-
});
|
|
830
|
+
}, issues, columnNodes);
|
|
763
831
|
continue;
|
|
764
832
|
}
|
|
765
833
|
columnNodes.push(verifyColumn({
|
|
@@ -769,42 +837,41 @@ function collectContractColumnNodes(options) {
|
|
|
769
837
|
contractColumn,
|
|
770
838
|
schemaColumn,
|
|
771
839
|
columnPath,
|
|
840
|
+
tableControlPolicy,
|
|
772
841
|
issues,
|
|
773
842
|
strict,
|
|
774
843
|
typeMetadataRegistry,
|
|
775
844
|
codecHooks,
|
|
776
845
|
storageTypes,
|
|
777
846
|
...ifDefined("normalizeDefault", normalizeDefault),
|
|
778
|
-
...ifDefined("normalizeNativeType", normalizeNativeType)
|
|
847
|
+
...ifDefined("normalizeNativeType", normalizeNativeType),
|
|
848
|
+
...ifDefined("columnsCompatible", columnsCompatible)
|
|
779
849
|
}));
|
|
780
850
|
}
|
|
781
851
|
return columnNodes;
|
|
782
852
|
}
|
|
783
853
|
function appendExtraColumnNodes(options) {
|
|
784
|
-
const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, columnNodes } = options;
|
|
785
|
-
for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
children: []
|
|
803
|
-
});
|
|
804
|
-
}
|
|
854
|
+
const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, columnNodes } = options;
|
|
855
|
+
for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
856
|
+
kind: "extra_column",
|
|
857
|
+
table: tableName,
|
|
858
|
+
namespaceId,
|
|
859
|
+
column: columnName,
|
|
860
|
+
message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`
|
|
861
|
+
}, {
|
|
862
|
+
status: "fail",
|
|
863
|
+
kind: "column",
|
|
864
|
+
name: `${columnName}: extra`,
|
|
865
|
+
contractPath: `${tablePath}.columns.${columnName}`,
|
|
866
|
+
code: "extra_column",
|
|
867
|
+
message: `Extra column "${columnName}" found`,
|
|
868
|
+
expected: void 0,
|
|
869
|
+
actual: nativeType,
|
|
870
|
+
children: []
|
|
871
|
+
}, issues, columnNodes);
|
|
805
872
|
}
|
|
806
873
|
function verifyColumn(options) {
|
|
807
|
-
const { tableName, namespaceId, columnName, contractColumn, schemaColumn, columnPath, issues, strict, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
874
|
+
const { tableName, namespaceId, columnName, contractColumn, schemaColumn, columnPath, tableControlPolicy, issues, strict, codecHooks, storageTypes, normalizeDefault, normalizeNativeType, columnsCompatible } = options;
|
|
808
875
|
const columnChildren = [];
|
|
809
876
|
let columnStatus = "pass";
|
|
810
877
|
const resolvedContractColumn = resolveContractColumnTypeMetadata(contractColumn, storageTypes, {
|
|
@@ -816,8 +883,8 @@ function verifyColumn(options) {
|
|
|
816
883
|
columnName
|
|
817
884
|
});
|
|
818
885
|
const schemaNativeType = normalizeNativeType?.(schemaColumn.nativeType) ?? schemaColumn.nativeType;
|
|
819
|
-
if (contractNativeType
|
|
820
|
-
|
|
886
|
+
if (!(contractNativeType === schemaNativeType || tableControlPolicy === "external" && (columnsCompatible?.(contractNativeType, schemaNativeType) ?? contractNativeType === schemaNativeType))) {
|
|
887
|
+
const issue = {
|
|
821
888
|
kind: "type_mismatch",
|
|
822
889
|
table: tableName,
|
|
823
890
|
namespaceId,
|
|
@@ -825,19 +892,23 @@ function verifyColumn(options) {
|
|
|
825
892
|
expected: contractNativeType,
|
|
826
893
|
actual: schemaNativeType,
|
|
827
894
|
message: `Column "${tableName}"."${columnName}" has type mismatch: expected "${contractNativeType}", got "${schemaNativeType}"`
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
895
|
+
};
|
|
896
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
897
|
+
if (disposition !== "suppress") {
|
|
898
|
+
issues.push(issue);
|
|
899
|
+
columnChildren.push({
|
|
900
|
+
status: disposition,
|
|
901
|
+
kind: "type",
|
|
902
|
+
name: "type",
|
|
903
|
+
contractPath: `${columnPath}.nativeType`,
|
|
904
|
+
code: "type_mismatch",
|
|
905
|
+
message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
|
|
906
|
+
expected: contractNativeType,
|
|
907
|
+
actual: schemaNativeType,
|
|
908
|
+
children: []
|
|
909
|
+
});
|
|
910
|
+
columnStatus = disposition;
|
|
911
|
+
}
|
|
841
912
|
}
|
|
842
913
|
if (resolvedContractColumn.codecId) {
|
|
843
914
|
const typeMetadata = options.typeMetadataRegistry.get(resolvedContractColumn.codecId);
|
|
@@ -865,7 +936,7 @@ function verifyColumn(options) {
|
|
|
865
936
|
});
|
|
866
937
|
}
|
|
867
938
|
if (contractColumn.nullable !== schemaColumn.nullable) {
|
|
868
|
-
|
|
939
|
+
const issue = {
|
|
869
940
|
kind: "nullability_mismatch",
|
|
870
941
|
table: tableName,
|
|
871
942
|
namespaceId,
|
|
@@ -873,47 +944,55 @@ function verifyColumn(options) {
|
|
|
873
944
|
expected: String(contractColumn.nullable),
|
|
874
945
|
actual: String(schemaColumn.nullable),
|
|
875
946
|
message: `Column "${tableName}"."${columnName}" has nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
947
|
+
};
|
|
948
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
949
|
+
if (disposition !== "suppress") {
|
|
950
|
+
issues.push(issue);
|
|
951
|
+
columnChildren.push({
|
|
952
|
+
status: disposition,
|
|
953
|
+
kind: "nullability",
|
|
954
|
+
name: "nullability",
|
|
955
|
+
contractPath: `${columnPath}.nullable`,
|
|
956
|
+
code: "nullability_mismatch",
|
|
957
|
+
message: `Nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`,
|
|
958
|
+
expected: contractColumn.nullable,
|
|
959
|
+
actual: schemaColumn.nullable,
|
|
960
|
+
children: []
|
|
961
|
+
});
|
|
962
|
+
columnStatus = disposition;
|
|
963
|
+
}
|
|
889
964
|
}
|
|
890
965
|
if (contractColumn.default) {
|
|
891
966
|
if (!schemaColumn.default) {
|
|
892
967
|
const defaultDescription = describeColumnDefault(contractColumn.default);
|
|
893
|
-
|
|
968
|
+
const issue = {
|
|
894
969
|
kind: "default_missing",
|
|
895
970
|
table: tableName,
|
|
896
971
|
namespaceId,
|
|
897
972
|
column: columnName,
|
|
898
973
|
expected: defaultDescription,
|
|
899
974
|
message: `Column "${tableName}"."${columnName}" should have default ${defaultDescription} but database has no default`
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
975
|
+
};
|
|
976
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
977
|
+
if (disposition !== "suppress") {
|
|
978
|
+
issues.push(issue);
|
|
979
|
+
columnChildren.push({
|
|
980
|
+
status: disposition,
|
|
981
|
+
kind: "default",
|
|
982
|
+
name: "default",
|
|
983
|
+
contractPath: `${columnPath}.default`,
|
|
984
|
+
code: "default_missing",
|
|
985
|
+
message: `Default missing: expected ${defaultDescription}`,
|
|
986
|
+
expected: defaultDescription,
|
|
987
|
+
actual: void 0,
|
|
988
|
+
children: []
|
|
989
|
+
});
|
|
990
|
+
columnStatus = disposition;
|
|
991
|
+
}
|
|
913
992
|
} else if (!columnDefaultsEqual(contractColumn.default, schemaColumn.default, normalizeDefault, schemaNativeType)) {
|
|
914
993
|
const expectedDescription = describeColumnDefault(contractColumn.default);
|
|
915
994
|
const actualDescription = schemaColumn.default;
|
|
916
|
-
|
|
995
|
+
const issue = {
|
|
917
996
|
kind: "default_mismatch",
|
|
918
997
|
table: tableName,
|
|
919
998
|
namespaceId,
|
|
@@ -921,41 +1000,49 @@ function verifyColumn(options) {
|
|
|
921
1000
|
expected: expectedDescription,
|
|
922
1001
|
actual: actualDescription,
|
|
923
1002
|
message: `Column "${tableName}"."${columnName}" has default mismatch: expected ${expectedDescription}, got ${actualDescription}`
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1003
|
+
};
|
|
1004
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
1005
|
+
if (disposition !== "suppress") {
|
|
1006
|
+
issues.push(issue);
|
|
1007
|
+
columnChildren.push({
|
|
1008
|
+
status: disposition,
|
|
1009
|
+
kind: "default",
|
|
1010
|
+
name: "default",
|
|
1011
|
+
contractPath: `${columnPath}.default`,
|
|
1012
|
+
code: "default_mismatch",
|
|
1013
|
+
message: `Default mismatch: expected ${expectedDescription}, got ${actualDescription}`,
|
|
1014
|
+
expected: expectedDescription,
|
|
1015
|
+
actual: actualDescription,
|
|
1016
|
+
children: []
|
|
1017
|
+
});
|
|
1018
|
+
columnStatus = disposition;
|
|
1019
|
+
}
|
|
937
1020
|
}
|
|
938
1021
|
} else if (strict && schemaColumn.default) {
|
|
939
|
-
|
|
1022
|
+
const issue = {
|
|
940
1023
|
kind: "extra_default",
|
|
941
1024
|
table: tableName,
|
|
942
1025
|
namespaceId,
|
|
943
1026
|
column: columnName,
|
|
944
1027
|
actual: schemaColumn.default,
|
|
945
1028
|
message: `Column "${tableName}"."${columnName}" has default ${schemaColumn.default} in database but contract specifies no default`
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1029
|
+
};
|
|
1030
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
1031
|
+
if (disposition !== "suppress") {
|
|
1032
|
+
issues.push(issue);
|
|
1033
|
+
columnChildren.push({
|
|
1034
|
+
status: disposition,
|
|
1035
|
+
kind: "default",
|
|
1036
|
+
name: "default",
|
|
1037
|
+
contractPath: `${columnPath}.default`,
|
|
1038
|
+
code: "extra_default",
|
|
1039
|
+
message: `Extra default: ${schemaColumn.default}`,
|
|
1040
|
+
expected: void 0,
|
|
1041
|
+
actual: schemaColumn.default,
|
|
1042
|
+
children: []
|
|
1043
|
+
});
|
|
1044
|
+
columnStatus = disposition;
|
|
1045
|
+
}
|
|
959
1046
|
}
|
|
960
1047
|
const aggregated = aggregateChildState(columnChildren, columnStatus);
|
|
961
1048
|
const nullableText = contractColumn.nullable ? "nullable" : "not nullable";
|
|
@@ -1154,4 +1241,4 @@ function formatLiteralValue(value) {
|
|
|
1154
1241
|
//#endregion
|
|
1155
1242
|
export { extractCodecControlHooks as a, isUniqueConstraintSatisfied as i, arraysEqual as n, isIndexSatisfied as r, verifySqlSchema as t };
|
|
1156
1243
|
|
|
1157
|
-
//# sourceMappingURL=verify-sql-schema-
|
|
1244
|
+
//# sourceMappingURL=verify-sql-schema-DV_d2XCG.mjs.map
|