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