@prisma-next/family-sql 0.12.0-dev.6 → 0.12.0-dev.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{authoring-type-constructors-F4JpCJl7.mjs → authoring-type-constructors-D4lQ-qpj.mjs} +1 -1
- package/dist/{authoring-type-constructors-F4JpCJl7.mjs.map → authoring-type-constructors-D4lQ-qpj.mjs.map} +1 -1
- package/dist/control-adapter-76pzcIFV.d.mts +172 -0
- package/dist/control-adapter-76pzcIFV.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 +305 -44
- package/dist/control.mjs.map +1 -1
- package/dist/ir.d.mts +4 -5
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/migration.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/runtime.d.mts +4 -2
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +4 -2
- 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-CY7qnms7.mjs} +18 -36
- package/dist/sql-contract-serializer-CY7qnms7.mjs.map +1 -0
- package/dist/{timestamp-now-generator-BkjCQIde.mjs → timestamp-now-generator-CloimujU.mjs} +1 -1
- package/dist/{timestamp-now-generator-BkjCQIde.mjs.map → timestamp-now-generator-CloimujU.mjs.map} +1 -1
- package/dist/{types-CeeCStqw.d.mts → types-CRpx_fk5.d.mts} +69 -15
- package/dist/types-CRpx_fk5.d.mts.map +1 -0
- package/dist/{verify-Crewz6hG.mjs → verify-C-G0obRm.mjs} +1 -1
- package/dist/{verify-Crewz6hG.mjs.map → verify-C-G0obRm.mjs.map} +1 -1
- package/dist/{verify-sql-schema-CN7pPoTC.d.mts → verify-sql-schema-DcMaT5Zj.d.mts} +1 -1
- package/dist/{verify-sql-schema-CN7pPoTC.d.mts.map → verify-sql-schema-DcMaT5Zj.d.mts.map} +1 -1
- package/dist/{verify-sql-schema-CYLsGCFO.mjs → verify-sql-schema-d3P9NA_8.mjs} +400 -317
- package/dist/verify-sql-schema-d3P9NA_8.mjs.map +1 -0
- package/dist/verify.mjs +1 -1
- package/package.json +23 -23
- package/src/core/control-adapter.ts +105 -7
- package/src/core/control-instance.ts +260 -66
- package/src/core/default-namespace.ts +9 -0
- package/src/core/ir/sql-contract-serializer-base.ts +72 -56
- package/src/core/migrations/contract-to-schema-ir.ts +13 -9
- 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 +16 -6
- 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 +281 -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-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
|
}
|
|
@@ -435,7 +486,7 @@ function verifySqlSchema(options) {
|
|
|
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({
|
|
@@ -450,53 +501,57 @@ function verifySqlSchema(options) {
|
|
|
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: []
|
|
465
527
|
});
|
|
466
528
|
};
|
|
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)) {
|
|
529
|
+
for (const [typeName, typeInstance] of Object.entries(contract.storage.types ?? {})) if (isStorageTypeInstance(typeInstance)) {
|
|
475
530
|
const hook = codecHooks.get(typeInstance.codecId);
|
|
476
531
|
pushTypeNode(typeName, `storage.types.${typeName}`, hook?.verifyType ? hook.verifyType({
|
|
477
532
|
typeName,
|
|
478
533
|
typeInstance,
|
|
479
534
|
schema
|
|
480
|
-
}) : []);
|
|
535
|
+
}) : [], effectiveControlPolicy(void 0, contract.defaultControlPolicy));
|
|
481
536
|
}
|
|
482
537
|
for (const nsId of Object.keys(contract.storage.namespaces)) {
|
|
483
538
|
const ns = contract.storage.namespaces[nsId];
|
|
484
539
|
if (!ns) continue;
|
|
485
|
-
const nsEnums = ns.
|
|
540
|
+
const nsEnums = ns.entries["type"];
|
|
486
541
|
if (!nsEnums) continue;
|
|
487
542
|
for (const [typeName, entry] of Object.entries(nsEnums)) {
|
|
488
543
|
if (!isPostgresEnumStorageEntry(entry)) continue;
|
|
489
|
-
pushTypeNode(typeName, `storage.namespaces.${nsId}.
|
|
544
|
+
pushTypeNode(typeName, `storage.namespaces.${nsId}.entries.type.${typeName}`, verifyEnumType({
|
|
490
545
|
typeName,
|
|
491
546
|
typeInstance: entry,
|
|
492
547
|
schema,
|
|
493
548
|
resolveExistingEnumValues,
|
|
494
549
|
namespaceId: nsId
|
|
495
|
-
}));
|
|
550
|
+
}), effectiveControlPolicy(entry.control, contract.defaultControlPolicy));
|
|
496
551
|
}
|
|
497
552
|
}
|
|
498
553
|
if (typeNodes.length > 0) {
|
|
499
|
-
const typesStatus = typeNodes.some((n) => n.status === "fail") ? "fail" : "pass";
|
|
554
|
+
const typesStatus = typeNodes.some((n) => n.status === "fail") ? "fail" : typeNodes.some((n) => n.status === "warn") ? "warn" : "pass";
|
|
500
555
|
rootChildren.push({
|
|
501
556
|
status: typesStatus,
|
|
502
557
|
kind: "storageTypes",
|
|
@@ -581,6 +636,7 @@ function extractContractMetadata(contract) {
|
|
|
581
636
|
}
|
|
582
637
|
function verifySchemaTables(options) {
|
|
583
638
|
const { contract, schema, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
639
|
+
const contractDefaultControl = contract.defaultControlPolicy;
|
|
584
640
|
const issues = [];
|
|
585
641
|
const rootChildren = [];
|
|
586
642
|
const schemaTables = schema.tables;
|
|
@@ -588,19 +644,19 @@ function verifySchemaTables(options) {
|
|
|
588
644
|
for (const namespaceId of namespaceIds) {
|
|
589
645
|
const ns = contract.storage.namespaces[namespaceId];
|
|
590
646
|
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}.
|
|
647
|
+
for (const [tableName, contractTableRaw] of Object.entries(ns.entries.table)) {
|
|
648
|
+
if (!(contractTableRaw instanceof StorageTable)) throw new Error(`verifySqlSchema: expected StorageTable at storage.namespaces.${namespaceId}.entries.table.${tableName}`);
|
|
593
649
|
const contractTable = contractTableRaw;
|
|
650
|
+
const tableControlPolicy = effectiveControlPolicy(contractTable.control, contractDefaultControl);
|
|
594
651
|
const schemaTable = schemaTables[tableName];
|
|
595
|
-
const tablePath = `storage.namespaces.${namespaceId}.
|
|
652
|
+
const tablePath = `storage.namespaces.${namespaceId}.entries.table.${tableName}`;
|
|
596
653
|
if (!schemaTable) {
|
|
597
|
-
|
|
654
|
+
emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
598
655
|
kind: "missing_table",
|
|
599
656
|
table: tableName,
|
|
600
657
|
namespaceId,
|
|
601
658
|
message: `Table "${tableName}" is missing from database`
|
|
602
|
-
}
|
|
603
|
-
rootChildren.push({
|
|
659
|
+
}, {
|
|
604
660
|
status: "fail",
|
|
605
661
|
kind: "table",
|
|
606
662
|
name: `table ${tableName}`,
|
|
@@ -610,7 +666,7 @@ function verifySchemaTables(options) {
|
|
|
610
666
|
expected: void 0,
|
|
611
667
|
actual: void 0,
|
|
612
668
|
children: []
|
|
613
|
-
});
|
|
669
|
+
}, issues, rootChildren);
|
|
614
670
|
continue;
|
|
615
671
|
}
|
|
616
672
|
const tableChildren = verifyTableChildren({
|
|
@@ -619,6 +675,7 @@ function verifySchemaTables(options) {
|
|
|
619
675
|
tableName,
|
|
620
676
|
namespaceId,
|
|
621
677
|
tablePath,
|
|
678
|
+
tableControlPolicy,
|
|
622
679
|
issues,
|
|
623
680
|
strict,
|
|
624
681
|
typeMetadataRegistry,
|
|
@@ -631,24 +688,21 @@ function verifySchemaTables(options) {
|
|
|
631
688
|
}
|
|
632
689
|
}
|
|
633
690
|
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
|
-
}
|
|
691
|
+
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), {
|
|
692
|
+
kind: "extra_table",
|
|
693
|
+
table: tableName,
|
|
694
|
+
message: `Extra table "${tableName}" found in database (not in contract)`
|
|
695
|
+
}, {
|
|
696
|
+
status: "fail",
|
|
697
|
+
kind: "table",
|
|
698
|
+
name: `table ${tableName}`,
|
|
699
|
+
contractPath: `storage.namespaces.*.entries.table.${tableName}`,
|
|
700
|
+
code: "extra_table",
|
|
701
|
+
message: `Extra table "${tableName}" found`,
|
|
702
|
+
expected: void 0,
|
|
703
|
+
actual: void 0,
|
|
704
|
+
children: []
|
|
705
|
+
}, issues, rootChildren);
|
|
652
706
|
}
|
|
653
707
|
return {
|
|
654
708
|
issues,
|
|
@@ -656,7 +710,7 @@ function verifySchemaTables(options) {
|
|
|
656
710
|
};
|
|
657
711
|
}
|
|
658
712
|
function verifyTableChildren(options) {
|
|
659
|
-
const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
713
|
+
const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
660
714
|
const tableChildren = [];
|
|
661
715
|
const columnNodes = collectContractColumnNodes({
|
|
662
716
|
contractTable,
|
|
@@ -664,6 +718,7 @@ function verifyTableChildren(options) {
|
|
|
664
718
|
tableName,
|
|
665
719
|
namespaceId,
|
|
666
720
|
tablePath,
|
|
721
|
+
tableControlPolicy,
|
|
667
722
|
issues,
|
|
668
723
|
strict,
|
|
669
724
|
typeMetadataRegistry,
|
|
@@ -679,77 +734,87 @@ function verifyTableChildren(options) {
|
|
|
679
734
|
tableName,
|
|
680
735
|
namespaceId,
|
|
681
736
|
tablePath,
|
|
737
|
+
tableControlPolicy,
|
|
682
738
|
issues,
|
|
683
739
|
columnNodes
|
|
684
740
|
});
|
|
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({
|
|
741
|
+
if (contractTable.primaryKey) {
|
|
742
|
+
const pkStatus = verifyPrimaryKey(contractTable.primaryKey, schemaTable.primaryKey, tableName, namespaceId, tableControlPolicy, issues);
|
|
743
|
+
if (pkStatus === "fail") tableChildren.push({
|
|
715
744
|
status: "fail",
|
|
716
745
|
kind: "primaryKey",
|
|
717
|
-
name: `primary key: ${
|
|
746
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
718
747
|
contractPath: `${tablePath}.primaryKey`,
|
|
719
|
-
code: "
|
|
720
|
-
message: "
|
|
721
|
-
expected:
|
|
748
|
+
code: "primary_key_mismatch",
|
|
749
|
+
message: "Primary key mismatch",
|
|
750
|
+
expected: contractTable.primaryKey,
|
|
722
751
|
actual: schemaTable.primaryKey,
|
|
723
752
|
children: []
|
|
724
753
|
});
|
|
725
|
-
|
|
754
|
+
else if (pkStatus === "warn") tableChildren.push({
|
|
755
|
+
status: "warn",
|
|
756
|
+
kind: "primaryKey",
|
|
757
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
758
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
759
|
+
code: "primary_key_mismatch",
|
|
760
|
+
message: "Primary key mismatch",
|
|
761
|
+
expected: contractTable.primaryKey,
|
|
762
|
+
actual: schemaTable.primaryKey,
|
|
763
|
+
children: []
|
|
764
|
+
});
|
|
765
|
+
else tableChildren.push({
|
|
766
|
+
status: "pass",
|
|
767
|
+
kind: "primaryKey",
|
|
768
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
769
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
770
|
+
code: "",
|
|
771
|
+
message: "",
|
|
772
|
+
expected: void 0,
|
|
773
|
+
actual: void 0,
|
|
774
|
+
children: []
|
|
775
|
+
});
|
|
776
|
+
} else if (schemaTable.primaryKey && strict) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
777
|
+
kind: "extra_primary_key",
|
|
778
|
+
table: tableName,
|
|
779
|
+
namespaceId,
|
|
780
|
+
message: "Extra primary key found in database (not in contract)"
|
|
781
|
+
}, {
|
|
782
|
+
status: "fail",
|
|
783
|
+
kind: "primaryKey",
|
|
784
|
+
name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
|
|
785
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
786
|
+
code: "extra_primary_key",
|
|
787
|
+
message: "Extra primary key found",
|
|
788
|
+
expected: void 0,
|
|
789
|
+
actual: schemaTable.primaryKey,
|
|
790
|
+
children: []
|
|
791
|
+
}, issues, tableChildren);
|
|
726
792
|
const constraintFks = contractTable.foreignKeys.filter((fk) => fk.constraint === true);
|
|
727
793
|
if (constraintFks.length > 0 || strict) {
|
|
728
|
-
const fkStatuses = verifyForeignKeys(constraintFks, schemaTable.foreignKeys, tableName, namespaceId, tablePath, issues, strict);
|
|
794
|
+
const fkStatuses = verifyForeignKeys(constraintFks, schemaTable.foreignKeys, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
|
|
729
795
|
tableChildren.push(...fkStatuses);
|
|
730
796
|
}
|
|
731
|
-
const uniqueStatuses = verifyUniqueConstraints(contractTable.uniques, schemaTable.uniques, schemaTable.indexes, tableName, namespaceId, tablePath, issues, strict);
|
|
797
|
+
const uniqueStatuses = verifyUniqueConstraints(contractTable.uniques, schemaTable.uniques, schemaTable.indexes, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
|
|
732
798
|
tableChildren.push(...uniqueStatuses);
|
|
733
799
|
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);
|
|
800
|
+
const indexStatuses = verifyIndexes([...contractTable.indexes, ...fkBackingIndexes], schemaTable.indexes, schemaTable.uniques, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict);
|
|
735
801
|
tableChildren.push(...indexStatuses);
|
|
736
802
|
return tableChildren;
|
|
737
803
|
}
|
|
738
804
|
function collectContractColumnNodes(options) {
|
|
739
|
-
const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
805
|
+
const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, strict, typeMetadataRegistry, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
740
806
|
const columnNodes = [];
|
|
741
807
|
for (const [columnName, contractColumn] of Object.entries(contractTable.columns)) {
|
|
742
808
|
const schemaColumn = schemaTable.columns[columnName];
|
|
743
809
|
const columnPath = `${tablePath}.columns.${columnName}`;
|
|
744
810
|
if (!schemaColumn) {
|
|
745
|
-
|
|
811
|
+
emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
746
812
|
kind: "missing_column",
|
|
747
813
|
table: tableName,
|
|
748
814
|
namespaceId,
|
|
749
815
|
column: columnName,
|
|
750
816
|
message: `Column "${tableName}"."${columnName}" is missing from database`
|
|
751
|
-
}
|
|
752
|
-
columnNodes.push({
|
|
817
|
+
}, {
|
|
753
818
|
status: "fail",
|
|
754
819
|
kind: "column",
|
|
755
820
|
name: `${columnName}: missing`,
|
|
@@ -759,7 +824,7 @@ function collectContractColumnNodes(options) {
|
|
|
759
824
|
expected: void 0,
|
|
760
825
|
actual: void 0,
|
|
761
826
|
children: []
|
|
762
|
-
});
|
|
827
|
+
}, issues, columnNodes);
|
|
763
828
|
continue;
|
|
764
829
|
}
|
|
765
830
|
columnNodes.push(verifyColumn({
|
|
@@ -769,6 +834,7 @@ function collectContractColumnNodes(options) {
|
|
|
769
834
|
contractColumn,
|
|
770
835
|
schemaColumn,
|
|
771
836
|
columnPath,
|
|
837
|
+
tableControlPolicy,
|
|
772
838
|
issues,
|
|
773
839
|
strict,
|
|
774
840
|
typeMetadataRegistry,
|
|
@@ -781,30 +847,27 @@ function collectContractColumnNodes(options) {
|
|
|
781
847
|
return columnNodes;
|
|
782
848
|
}
|
|
783
849
|
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
|
-
}
|
|
850
|
+
const { contractTable, schemaTable, tableName, namespaceId, tablePath, tableControlPolicy, issues, columnNodes } = options;
|
|
851
|
+
for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) if (!contractTable.columns[columnName]) emitIssueAndNodeUnderControlPolicy(tableControlPolicy, {
|
|
852
|
+
kind: "extra_column",
|
|
853
|
+
table: tableName,
|
|
854
|
+
namespaceId,
|
|
855
|
+
column: columnName,
|
|
856
|
+
message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`
|
|
857
|
+
}, {
|
|
858
|
+
status: "fail",
|
|
859
|
+
kind: "column",
|
|
860
|
+
name: `${columnName}: extra`,
|
|
861
|
+
contractPath: `${tablePath}.columns.${columnName}`,
|
|
862
|
+
code: "extra_column",
|
|
863
|
+
message: `Extra column "${columnName}" found`,
|
|
864
|
+
expected: void 0,
|
|
865
|
+
actual: nativeType,
|
|
866
|
+
children: []
|
|
867
|
+
}, issues, columnNodes);
|
|
805
868
|
}
|
|
806
869
|
function verifyColumn(options) {
|
|
807
|
-
const { tableName, namespaceId, columnName, contractColumn, schemaColumn, columnPath, issues, strict, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
870
|
+
const { tableName, namespaceId, columnName, contractColumn, schemaColumn, columnPath, tableControlPolicy, issues, strict, codecHooks, storageTypes, normalizeDefault, normalizeNativeType } = options;
|
|
808
871
|
const columnChildren = [];
|
|
809
872
|
let columnStatus = "pass";
|
|
810
873
|
const resolvedContractColumn = resolveContractColumnTypeMetadata(contractColumn, storageTypes, {
|
|
@@ -816,8 +879,8 @@ function verifyColumn(options) {
|
|
|
816
879
|
columnName
|
|
817
880
|
});
|
|
818
881
|
const schemaNativeType = normalizeNativeType?.(schemaColumn.nativeType) ?? schemaColumn.nativeType;
|
|
819
|
-
if (contractNativeType
|
|
820
|
-
|
|
882
|
+
if (!(contractNativeType === schemaNativeType)) {
|
|
883
|
+
const issue = {
|
|
821
884
|
kind: "type_mismatch",
|
|
822
885
|
table: tableName,
|
|
823
886
|
namespaceId,
|
|
@@ -825,19 +888,23 @@ function verifyColumn(options) {
|
|
|
825
888
|
expected: contractNativeType,
|
|
826
889
|
actual: schemaNativeType,
|
|
827
890
|
message: `Column "${tableName}"."${columnName}" has type mismatch: expected "${contractNativeType}", got "${schemaNativeType}"`
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
891
|
+
};
|
|
892
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
893
|
+
if (disposition !== "suppress") {
|
|
894
|
+
issues.push(issue);
|
|
895
|
+
columnChildren.push({
|
|
896
|
+
status: disposition,
|
|
897
|
+
kind: "type",
|
|
898
|
+
name: "type",
|
|
899
|
+
contractPath: `${columnPath}.nativeType`,
|
|
900
|
+
code: "type_mismatch",
|
|
901
|
+
message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
|
|
902
|
+
expected: contractNativeType,
|
|
903
|
+
actual: schemaNativeType,
|
|
904
|
+
children: []
|
|
905
|
+
});
|
|
906
|
+
columnStatus = disposition;
|
|
907
|
+
}
|
|
841
908
|
}
|
|
842
909
|
if (resolvedContractColumn.codecId) {
|
|
843
910
|
const typeMetadata = options.typeMetadataRegistry.get(resolvedContractColumn.codecId);
|
|
@@ -865,7 +932,7 @@ function verifyColumn(options) {
|
|
|
865
932
|
});
|
|
866
933
|
}
|
|
867
934
|
if (contractColumn.nullable !== schemaColumn.nullable) {
|
|
868
|
-
|
|
935
|
+
const issue = {
|
|
869
936
|
kind: "nullability_mismatch",
|
|
870
937
|
table: tableName,
|
|
871
938
|
namespaceId,
|
|
@@ -873,47 +940,55 @@ function verifyColumn(options) {
|
|
|
873
940
|
expected: String(contractColumn.nullable),
|
|
874
941
|
actual: String(schemaColumn.nullable),
|
|
875
942
|
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
|
-
|
|
943
|
+
};
|
|
944
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
945
|
+
if (disposition !== "suppress") {
|
|
946
|
+
issues.push(issue);
|
|
947
|
+
columnChildren.push({
|
|
948
|
+
status: disposition,
|
|
949
|
+
kind: "nullability",
|
|
950
|
+
name: "nullability",
|
|
951
|
+
contractPath: `${columnPath}.nullable`,
|
|
952
|
+
code: "nullability_mismatch",
|
|
953
|
+
message: `Nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`,
|
|
954
|
+
expected: contractColumn.nullable,
|
|
955
|
+
actual: schemaColumn.nullable,
|
|
956
|
+
children: []
|
|
957
|
+
});
|
|
958
|
+
columnStatus = disposition;
|
|
959
|
+
}
|
|
889
960
|
}
|
|
890
961
|
if (contractColumn.default) {
|
|
891
962
|
if (!schemaColumn.default) {
|
|
892
963
|
const defaultDescription = describeColumnDefault(contractColumn.default);
|
|
893
|
-
|
|
964
|
+
const issue = {
|
|
894
965
|
kind: "default_missing",
|
|
895
966
|
table: tableName,
|
|
896
967
|
namespaceId,
|
|
897
968
|
column: columnName,
|
|
898
969
|
expected: defaultDescription,
|
|
899
970
|
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
|
-
|
|
971
|
+
};
|
|
972
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
973
|
+
if (disposition !== "suppress") {
|
|
974
|
+
issues.push(issue);
|
|
975
|
+
columnChildren.push({
|
|
976
|
+
status: disposition,
|
|
977
|
+
kind: "default",
|
|
978
|
+
name: "default",
|
|
979
|
+
contractPath: `${columnPath}.default`,
|
|
980
|
+
code: "default_missing",
|
|
981
|
+
message: `Default missing: expected ${defaultDescription}`,
|
|
982
|
+
expected: defaultDescription,
|
|
983
|
+
actual: void 0,
|
|
984
|
+
children: []
|
|
985
|
+
});
|
|
986
|
+
columnStatus = disposition;
|
|
987
|
+
}
|
|
913
988
|
} else if (!columnDefaultsEqual(contractColumn.default, schemaColumn.default, normalizeDefault, schemaNativeType)) {
|
|
914
989
|
const expectedDescription = describeColumnDefault(contractColumn.default);
|
|
915
990
|
const actualDescription = schemaColumn.default;
|
|
916
|
-
|
|
991
|
+
const issue = {
|
|
917
992
|
kind: "default_mismatch",
|
|
918
993
|
table: tableName,
|
|
919
994
|
namespaceId,
|
|
@@ -921,41 +996,49 @@ function verifyColumn(options) {
|
|
|
921
996
|
expected: expectedDescription,
|
|
922
997
|
actual: actualDescription,
|
|
923
998
|
message: `Column "${tableName}"."${columnName}" has default mismatch: expected ${expectedDescription}, got ${actualDescription}`
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
999
|
+
};
|
|
1000
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
1001
|
+
if (disposition !== "suppress") {
|
|
1002
|
+
issues.push(issue);
|
|
1003
|
+
columnChildren.push({
|
|
1004
|
+
status: disposition,
|
|
1005
|
+
kind: "default",
|
|
1006
|
+
name: "default",
|
|
1007
|
+
contractPath: `${columnPath}.default`,
|
|
1008
|
+
code: "default_mismatch",
|
|
1009
|
+
message: `Default mismatch: expected ${expectedDescription}, got ${actualDescription}`,
|
|
1010
|
+
expected: expectedDescription,
|
|
1011
|
+
actual: actualDescription,
|
|
1012
|
+
children: []
|
|
1013
|
+
});
|
|
1014
|
+
columnStatus = disposition;
|
|
1015
|
+
}
|
|
937
1016
|
}
|
|
938
1017
|
} else if (strict && schemaColumn.default) {
|
|
939
|
-
|
|
1018
|
+
const issue = {
|
|
940
1019
|
kind: "extra_default",
|
|
941
1020
|
table: tableName,
|
|
942
1021
|
namespaceId,
|
|
943
1022
|
column: columnName,
|
|
944
1023
|
actual: schemaColumn.default,
|
|
945
1024
|
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
|
-
|
|
1025
|
+
};
|
|
1026
|
+
const disposition = verifierDisposition(tableControlPolicy, issue.kind);
|
|
1027
|
+
if (disposition !== "suppress") {
|
|
1028
|
+
issues.push(issue);
|
|
1029
|
+
columnChildren.push({
|
|
1030
|
+
status: disposition,
|
|
1031
|
+
kind: "default",
|
|
1032
|
+
name: "default",
|
|
1033
|
+
contractPath: `${columnPath}.default`,
|
|
1034
|
+
code: "extra_default",
|
|
1035
|
+
message: `Extra default: ${schemaColumn.default}`,
|
|
1036
|
+
expected: void 0,
|
|
1037
|
+
actual: schemaColumn.default,
|
|
1038
|
+
children: []
|
|
1039
|
+
});
|
|
1040
|
+
columnStatus = disposition;
|
|
1041
|
+
}
|
|
959
1042
|
}
|
|
960
1043
|
const aggregated = aggregateChildState(columnChildren, columnStatus);
|
|
961
1044
|
const nullableText = contractColumn.nullable ? "nullable" : "not nullable";
|
|
@@ -1154,4 +1237,4 @@ function formatLiteralValue(value) {
|
|
|
1154
1237
|
//#endregion
|
|
1155
1238
|
export { extractCodecControlHooks as a, isUniqueConstraintSatisfied as i, arraysEqual as n, isIndexSatisfied as r, verifySqlSchema as t };
|
|
1156
1239
|
|
|
1157
|
-
//# sourceMappingURL=verify-sql-schema-
|
|
1240
|
+
//# sourceMappingURL=verify-sql-schema-d3P9NA_8.mjs.map
|