@prisma-next/family-sql 0.1.0-dev.15 → 0.1.0-dev.16
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/exports/chunk-I7QWTBWS.js +764 -0
- package/dist/exports/chunk-I7QWTBWS.js.map +1 -0
- package/dist/exports/chunk-MHDZIQUL.js +608 -0
- package/dist/exports/chunk-MHDZIQUL.js.map +1 -0
- package/dist/exports/control.d.ts +1 -1
- package/dist/exports/control.js +3 -2
- package/dist/exports/control.js.map +1 -1
- package/dist/exports/schema-verify.d.ts +42 -0
- package/dist/exports/schema-verify.js +7 -0
- package/dist/exports/schema-verify.js.map +1 -0
- package/dist/exports/test-utils.js +2 -1
- package/package.json +20 -17
- package/dist/exports/chunk-FDYFNYMY.js +0 -1389
- package/dist/exports/chunk-FDYFNYMY.js.map +0 -1
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
// src/core/schema-verify/verify-sql-schema.ts
|
|
2
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
3
|
+
|
|
4
|
+
// src/core/schema-verify/verify-helpers.ts
|
|
5
|
+
function arraysEqual(a, b) {
|
|
6
|
+
if (a.length !== b.length) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
for (let i = 0; i < a.length; i++) {
|
|
10
|
+
if (a[i] !== b[i]) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
function verifyPrimaryKey(contractPK, schemaPK, tableName, issues) {
|
|
17
|
+
if (!schemaPK) {
|
|
18
|
+
issues.push({
|
|
19
|
+
kind: "primary_key_mismatch",
|
|
20
|
+
table: tableName,
|
|
21
|
+
expected: contractPK.columns.join(", "),
|
|
22
|
+
message: `Table "${tableName}" is missing primary key`
|
|
23
|
+
});
|
|
24
|
+
return "fail";
|
|
25
|
+
}
|
|
26
|
+
if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
|
|
27
|
+
issues.push({
|
|
28
|
+
kind: "primary_key_mismatch",
|
|
29
|
+
table: tableName,
|
|
30
|
+
expected: contractPK.columns.join(", "),
|
|
31
|
+
actual: schemaPK.columns.join(", "),
|
|
32
|
+
message: `Table "${tableName}" has primary key mismatch: expected columns [${contractPK.columns.join(", ")}], got [${schemaPK.columns.join(", ")}]`
|
|
33
|
+
});
|
|
34
|
+
return "fail";
|
|
35
|
+
}
|
|
36
|
+
if (contractPK.name && schemaPK.name && contractPK.name !== schemaPK.name) {
|
|
37
|
+
issues.push({
|
|
38
|
+
kind: "primary_key_mismatch",
|
|
39
|
+
table: tableName,
|
|
40
|
+
indexOrConstraint: contractPK.name,
|
|
41
|
+
expected: contractPK.name,
|
|
42
|
+
actual: schemaPK.name,
|
|
43
|
+
message: `Table "${tableName}" has primary key name mismatch: expected "${contractPK.name}", got "${schemaPK.name}"`
|
|
44
|
+
});
|
|
45
|
+
return "fail";
|
|
46
|
+
}
|
|
47
|
+
return "pass";
|
|
48
|
+
}
|
|
49
|
+
function verifyForeignKeys(contractFKs, schemaFKs, tableName, tablePath, issues, strict) {
|
|
50
|
+
const nodes = [];
|
|
51
|
+
for (const contractFK of contractFKs) {
|
|
52
|
+
const fkPath = `${tablePath}.foreignKeys[${contractFK.columns.join(",")}]`;
|
|
53
|
+
const matchingFK = schemaFKs.find((fk) => {
|
|
54
|
+
return arraysEqual(fk.columns, contractFK.columns) && fk.referencedTable === contractFK.references.table && arraysEqual(fk.referencedColumns, contractFK.references.columns);
|
|
55
|
+
});
|
|
56
|
+
if (!matchingFK) {
|
|
57
|
+
issues.push({
|
|
58
|
+
kind: "foreign_key_mismatch",
|
|
59
|
+
table: tableName,
|
|
60
|
+
expected: `${contractFK.columns.join(", ")} -> ${contractFK.references.table}(${contractFK.references.columns.join(", ")})`,
|
|
61
|
+
message: `Table "${tableName}" is missing foreign key: ${contractFK.columns.join(", ")} -> ${contractFK.references.table}(${contractFK.references.columns.join(", ")})`
|
|
62
|
+
});
|
|
63
|
+
nodes.push({
|
|
64
|
+
status: "fail",
|
|
65
|
+
kind: "foreignKey",
|
|
66
|
+
name: `foreignKey(${contractFK.columns.join(", ")})`,
|
|
67
|
+
contractPath: fkPath,
|
|
68
|
+
code: "foreign_key_mismatch",
|
|
69
|
+
message: "Foreign key missing",
|
|
70
|
+
expected: contractFK,
|
|
71
|
+
actual: void 0,
|
|
72
|
+
children: []
|
|
73
|
+
});
|
|
74
|
+
} else {
|
|
75
|
+
if (contractFK.name && matchingFK.name && contractFK.name !== matchingFK.name) {
|
|
76
|
+
issues.push({
|
|
77
|
+
kind: "foreign_key_mismatch",
|
|
78
|
+
table: tableName,
|
|
79
|
+
indexOrConstraint: contractFK.name,
|
|
80
|
+
expected: contractFK.name,
|
|
81
|
+
actual: matchingFK.name,
|
|
82
|
+
message: `Table "${tableName}" has foreign key name mismatch: expected "${contractFK.name}", got "${matchingFK.name}"`
|
|
83
|
+
});
|
|
84
|
+
nodes.push({
|
|
85
|
+
status: "fail",
|
|
86
|
+
kind: "foreignKey",
|
|
87
|
+
name: `foreignKey(${contractFK.columns.join(", ")})`,
|
|
88
|
+
contractPath: fkPath,
|
|
89
|
+
code: "foreign_key_mismatch",
|
|
90
|
+
message: "Foreign key name mismatch",
|
|
91
|
+
expected: contractFK.name,
|
|
92
|
+
actual: matchingFK.name,
|
|
93
|
+
children: []
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
nodes.push({
|
|
97
|
+
status: "pass",
|
|
98
|
+
kind: "foreignKey",
|
|
99
|
+
name: `foreignKey(${contractFK.columns.join(", ")})`,
|
|
100
|
+
contractPath: fkPath,
|
|
101
|
+
code: "",
|
|
102
|
+
message: "",
|
|
103
|
+
expected: void 0,
|
|
104
|
+
actual: void 0,
|
|
105
|
+
children: []
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (strict) {
|
|
111
|
+
for (const schemaFK of schemaFKs) {
|
|
112
|
+
const matchingFK = contractFKs.find((fk) => {
|
|
113
|
+
return arraysEqual(fk.columns, schemaFK.columns) && fk.references.table === schemaFK.referencedTable && arraysEqual(fk.references.columns, schemaFK.referencedColumns);
|
|
114
|
+
});
|
|
115
|
+
if (!matchingFK) {
|
|
116
|
+
issues.push({
|
|
117
|
+
kind: "extra_foreign_key",
|
|
118
|
+
table: tableName,
|
|
119
|
+
message: `Extra foreign key found in database (not in contract): ${schemaFK.columns.join(", ")} -> ${schemaFK.referencedTable}(${schemaFK.referencedColumns.join(", ")})`
|
|
120
|
+
});
|
|
121
|
+
nodes.push({
|
|
122
|
+
status: "fail",
|
|
123
|
+
kind: "foreignKey",
|
|
124
|
+
name: `foreignKey(${schemaFK.columns.join(", ")})`,
|
|
125
|
+
contractPath: `${tablePath}.foreignKeys[${schemaFK.columns.join(",")}]`,
|
|
126
|
+
code: "extra_foreign_key",
|
|
127
|
+
message: "Extra foreign key found",
|
|
128
|
+
expected: void 0,
|
|
129
|
+
actual: schemaFK,
|
|
130
|
+
children: []
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return nodes;
|
|
136
|
+
}
|
|
137
|
+
function verifyUniqueConstraints(contractUniques, schemaUniques, tableName, tablePath, issues, strict) {
|
|
138
|
+
const nodes = [];
|
|
139
|
+
for (const contractUnique of contractUniques) {
|
|
140
|
+
const uniquePath = `${tablePath}.uniques[${contractUnique.columns.join(",")}]`;
|
|
141
|
+
const matchingUnique = schemaUniques.find(
|
|
142
|
+
(u) => arraysEqual(u.columns, contractUnique.columns)
|
|
143
|
+
);
|
|
144
|
+
if (!matchingUnique) {
|
|
145
|
+
issues.push({
|
|
146
|
+
kind: "unique_constraint_mismatch",
|
|
147
|
+
table: tableName,
|
|
148
|
+
expected: contractUnique.columns.join(", "),
|
|
149
|
+
message: `Table "${tableName}" is missing unique constraint: ${contractUnique.columns.join(", ")}`
|
|
150
|
+
});
|
|
151
|
+
nodes.push({
|
|
152
|
+
status: "fail",
|
|
153
|
+
kind: "unique",
|
|
154
|
+
name: `unique(${contractUnique.columns.join(", ")})`,
|
|
155
|
+
contractPath: uniquePath,
|
|
156
|
+
code: "unique_constraint_mismatch",
|
|
157
|
+
message: "Unique constraint missing",
|
|
158
|
+
expected: contractUnique,
|
|
159
|
+
actual: void 0,
|
|
160
|
+
children: []
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
if (contractUnique.name && matchingUnique.name && contractUnique.name !== matchingUnique.name) {
|
|
164
|
+
issues.push({
|
|
165
|
+
kind: "unique_constraint_mismatch",
|
|
166
|
+
table: tableName,
|
|
167
|
+
indexOrConstraint: contractUnique.name,
|
|
168
|
+
expected: contractUnique.name,
|
|
169
|
+
actual: matchingUnique.name,
|
|
170
|
+
message: `Table "${tableName}" has unique constraint name mismatch: expected "${contractUnique.name}", got "${matchingUnique.name}"`
|
|
171
|
+
});
|
|
172
|
+
nodes.push({
|
|
173
|
+
status: "fail",
|
|
174
|
+
kind: "unique",
|
|
175
|
+
name: `unique(${contractUnique.columns.join(", ")})`,
|
|
176
|
+
contractPath: uniquePath,
|
|
177
|
+
code: "unique_constraint_mismatch",
|
|
178
|
+
message: "Unique constraint name mismatch",
|
|
179
|
+
expected: contractUnique.name,
|
|
180
|
+
actual: matchingUnique.name,
|
|
181
|
+
children: []
|
|
182
|
+
});
|
|
183
|
+
} else {
|
|
184
|
+
nodes.push({
|
|
185
|
+
status: "pass",
|
|
186
|
+
kind: "unique",
|
|
187
|
+
name: `unique(${contractUnique.columns.join(", ")})`,
|
|
188
|
+
contractPath: uniquePath,
|
|
189
|
+
code: "",
|
|
190
|
+
message: "",
|
|
191
|
+
expected: void 0,
|
|
192
|
+
actual: void 0,
|
|
193
|
+
children: []
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (strict) {
|
|
199
|
+
for (const schemaUnique of schemaUniques) {
|
|
200
|
+
const matchingUnique = contractUniques.find(
|
|
201
|
+
(u) => arraysEqual(u.columns, schemaUnique.columns)
|
|
202
|
+
);
|
|
203
|
+
if (!matchingUnique) {
|
|
204
|
+
issues.push({
|
|
205
|
+
kind: "extra_unique_constraint",
|
|
206
|
+
table: tableName,
|
|
207
|
+
message: `Extra unique constraint found in database (not in contract): ${schemaUnique.columns.join(", ")}`
|
|
208
|
+
});
|
|
209
|
+
nodes.push({
|
|
210
|
+
status: "fail",
|
|
211
|
+
kind: "unique",
|
|
212
|
+
name: `unique(${schemaUnique.columns.join(", ")})`,
|
|
213
|
+
contractPath: `${tablePath}.uniques[${schemaUnique.columns.join(",")}]`,
|
|
214
|
+
code: "extra_unique_constraint",
|
|
215
|
+
message: "Extra unique constraint found",
|
|
216
|
+
expected: void 0,
|
|
217
|
+
actual: schemaUnique,
|
|
218
|
+
children: []
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return nodes;
|
|
224
|
+
}
|
|
225
|
+
function verifyIndexes(contractIndexes, schemaIndexes, tableName, tablePath, issues, strict) {
|
|
226
|
+
const nodes = [];
|
|
227
|
+
for (const contractIndex of contractIndexes) {
|
|
228
|
+
const indexPath = `${tablePath}.indexes[${contractIndex.columns.join(",")}]`;
|
|
229
|
+
const matchingIndex = schemaIndexes.find(
|
|
230
|
+
(idx) => arraysEqual(idx.columns, contractIndex.columns) && idx.unique === false
|
|
231
|
+
);
|
|
232
|
+
if (!matchingIndex) {
|
|
233
|
+
issues.push({
|
|
234
|
+
kind: "index_mismatch",
|
|
235
|
+
table: tableName,
|
|
236
|
+
expected: contractIndex.columns.join(", "),
|
|
237
|
+
message: `Table "${tableName}" is missing index: ${contractIndex.columns.join(", ")}`
|
|
238
|
+
});
|
|
239
|
+
nodes.push({
|
|
240
|
+
status: "fail",
|
|
241
|
+
kind: "index",
|
|
242
|
+
name: `index(${contractIndex.columns.join(", ")})`,
|
|
243
|
+
contractPath: indexPath,
|
|
244
|
+
code: "index_mismatch",
|
|
245
|
+
message: "Index missing",
|
|
246
|
+
expected: contractIndex,
|
|
247
|
+
actual: void 0,
|
|
248
|
+
children: []
|
|
249
|
+
});
|
|
250
|
+
} else {
|
|
251
|
+
if (contractIndex.name && matchingIndex.name && contractIndex.name !== matchingIndex.name) {
|
|
252
|
+
issues.push({
|
|
253
|
+
kind: "index_mismatch",
|
|
254
|
+
table: tableName,
|
|
255
|
+
indexOrConstraint: contractIndex.name,
|
|
256
|
+
expected: contractIndex.name,
|
|
257
|
+
actual: matchingIndex.name,
|
|
258
|
+
message: `Table "${tableName}" has index name mismatch: expected "${contractIndex.name}", got "${matchingIndex.name}"`
|
|
259
|
+
});
|
|
260
|
+
nodes.push({
|
|
261
|
+
status: "fail",
|
|
262
|
+
kind: "index",
|
|
263
|
+
name: `index(${contractIndex.columns.join(", ")})`,
|
|
264
|
+
contractPath: indexPath,
|
|
265
|
+
code: "index_mismatch",
|
|
266
|
+
message: "Index name mismatch",
|
|
267
|
+
expected: contractIndex.name,
|
|
268
|
+
actual: matchingIndex.name,
|
|
269
|
+
children: []
|
|
270
|
+
});
|
|
271
|
+
} else {
|
|
272
|
+
nodes.push({
|
|
273
|
+
status: "pass",
|
|
274
|
+
kind: "index",
|
|
275
|
+
name: `index(${contractIndex.columns.join(", ")})`,
|
|
276
|
+
contractPath: indexPath,
|
|
277
|
+
code: "",
|
|
278
|
+
message: "",
|
|
279
|
+
expected: void 0,
|
|
280
|
+
actual: void 0,
|
|
281
|
+
children: []
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (strict) {
|
|
287
|
+
for (const schemaIndex of schemaIndexes) {
|
|
288
|
+
if (schemaIndex.unique) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const matchingIndex = contractIndexes.find(
|
|
292
|
+
(idx) => arraysEqual(idx.columns, schemaIndex.columns)
|
|
293
|
+
);
|
|
294
|
+
if (!matchingIndex) {
|
|
295
|
+
issues.push({
|
|
296
|
+
kind: "extra_index",
|
|
297
|
+
table: tableName,
|
|
298
|
+
message: `Extra index found in database (not in contract): ${schemaIndex.columns.join(", ")}`
|
|
299
|
+
});
|
|
300
|
+
nodes.push({
|
|
301
|
+
status: "fail",
|
|
302
|
+
kind: "index",
|
|
303
|
+
name: `index(${schemaIndex.columns.join(", ")})`,
|
|
304
|
+
contractPath: `${tablePath}.indexes[${schemaIndex.columns.join(",")}]`,
|
|
305
|
+
code: "extra_index",
|
|
306
|
+
message: "Extra index found",
|
|
307
|
+
expected: void 0,
|
|
308
|
+
actual: schemaIndex,
|
|
309
|
+
children: []
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return nodes;
|
|
315
|
+
}
|
|
316
|
+
function verifyExtensions(contractExtensions, schemaExtensions, contractTarget, issues, _strict) {
|
|
317
|
+
const nodes = [];
|
|
318
|
+
if (!contractExtensions) {
|
|
319
|
+
return nodes;
|
|
320
|
+
}
|
|
321
|
+
const contractExtensionNames = Object.keys(contractExtensions).filter(
|
|
322
|
+
(name) => name !== contractTarget
|
|
323
|
+
);
|
|
324
|
+
for (const extName of contractExtensionNames) {
|
|
325
|
+
const extPath = `extensions.${extName}`;
|
|
326
|
+
const normalizedExtName = extName.toLowerCase().replace(/^pg/, "");
|
|
327
|
+
const matchingExt = schemaExtensions.find((e) => {
|
|
328
|
+
const normalizedE = e.toLowerCase();
|
|
329
|
+
if (normalizedE === normalizedExtName || normalizedE === extName.toLowerCase()) {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
if (normalizedE.includes(normalizedExtName) || normalizedExtName.includes(normalizedE)) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
});
|
|
337
|
+
const extensionLabels = {
|
|
338
|
+
pg: "database is postgres",
|
|
339
|
+
pgvector: "vector extension is enabled",
|
|
340
|
+
vector: "vector extension is enabled"
|
|
341
|
+
};
|
|
342
|
+
const extensionLabel = extensionLabels[extName] ?? `extension "${extName}" is enabled`;
|
|
343
|
+
if (!matchingExt) {
|
|
344
|
+
issues.push({
|
|
345
|
+
kind: "extension_missing",
|
|
346
|
+
table: "",
|
|
347
|
+
message: `Extension "${extName}" is missing from database`
|
|
348
|
+
});
|
|
349
|
+
nodes.push({
|
|
350
|
+
status: "fail",
|
|
351
|
+
kind: "extension",
|
|
352
|
+
name: extensionLabel,
|
|
353
|
+
contractPath: extPath,
|
|
354
|
+
code: "extension_missing",
|
|
355
|
+
message: `Extension "${extName}" is missing`,
|
|
356
|
+
expected: void 0,
|
|
357
|
+
actual: void 0,
|
|
358
|
+
children: []
|
|
359
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
nodes.push({
|
|
362
|
+
status: "pass",
|
|
363
|
+
kind: "extension",
|
|
364
|
+
name: extensionLabel,
|
|
365
|
+
contractPath: extPath,
|
|
366
|
+
code: "",
|
|
367
|
+
message: "",
|
|
368
|
+
expected: void 0,
|
|
369
|
+
actual: void 0,
|
|
370
|
+
children: []
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return nodes;
|
|
375
|
+
}
|
|
376
|
+
function computeCounts(node) {
|
|
377
|
+
let pass = 0;
|
|
378
|
+
let warn = 0;
|
|
379
|
+
let fail = 0;
|
|
380
|
+
function traverse(n) {
|
|
381
|
+
if (n.status === "pass") {
|
|
382
|
+
pass++;
|
|
383
|
+
} else if (n.status === "warn") {
|
|
384
|
+
warn++;
|
|
385
|
+
} else if (n.status === "fail") {
|
|
386
|
+
fail++;
|
|
387
|
+
}
|
|
388
|
+
if (n.children) {
|
|
389
|
+
for (const child of n.children) {
|
|
390
|
+
traverse(child);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
traverse(node);
|
|
395
|
+
return {
|
|
396
|
+
pass,
|
|
397
|
+
warn,
|
|
398
|
+
fail,
|
|
399
|
+
totalNodes: pass + warn + fail
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/core/schema-verify/verify-sql-schema.ts
|
|
404
|
+
function verifySqlSchema(options) {
|
|
405
|
+
const { contract, schema, strict, context, typeMetadataRegistry } = options;
|
|
406
|
+
const startTime = Date.now();
|
|
407
|
+
const contractCoreHash = contract.coreHash;
|
|
408
|
+
const contractProfileHash = "profileHash" in contract && typeof contract.profileHash === "string" ? contract.profileHash : void 0;
|
|
409
|
+
const contractTarget = contract.target;
|
|
410
|
+
const issues = [];
|
|
411
|
+
const rootChildren = [];
|
|
412
|
+
const contractTables = contract.storage.tables;
|
|
413
|
+
const schemaTables = schema.tables;
|
|
414
|
+
for (const [tableName, contractTable] of Object.entries(contractTables)) {
|
|
415
|
+
const schemaTable = schemaTables[tableName];
|
|
416
|
+
const tablePath = `storage.tables.${tableName}`;
|
|
417
|
+
if (!schemaTable) {
|
|
418
|
+
issues.push({
|
|
419
|
+
kind: "missing_table",
|
|
420
|
+
table: tableName,
|
|
421
|
+
message: `Table "${tableName}" is missing from database`
|
|
422
|
+
});
|
|
423
|
+
rootChildren.push({
|
|
424
|
+
status: "fail",
|
|
425
|
+
kind: "table",
|
|
426
|
+
name: `table ${tableName}`,
|
|
427
|
+
contractPath: tablePath,
|
|
428
|
+
code: "missing_table",
|
|
429
|
+
message: `Table "${tableName}" is missing`,
|
|
430
|
+
expected: void 0,
|
|
431
|
+
actual: void 0,
|
|
432
|
+
children: []
|
|
433
|
+
});
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
const tableChildren = [];
|
|
437
|
+
const columnNodes = [];
|
|
438
|
+
for (const [columnName, contractColumn] of Object.entries(contractTable.columns)) {
|
|
439
|
+
const schemaColumn = schemaTable.columns[columnName];
|
|
440
|
+
const columnPath = `${tablePath}.columns.${columnName}`;
|
|
441
|
+
if (!schemaColumn) {
|
|
442
|
+
issues.push({
|
|
443
|
+
kind: "missing_column",
|
|
444
|
+
table: tableName,
|
|
445
|
+
column: columnName,
|
|
446
|
+
message: `Column "${tableName}"."${columnName}" is missing from database`
|
|
447
|
+
});
|
|
448
|
+
columnNodes.push({
|
|
449
|
+
status: "fail",
|
|
450
|
+
kind: "column",
|
|
451
|
+
name: `${columnName}: missing`,
|
|
452
|
+
contractPath: columnPath,
|
|
453
|
+
code: "missing_column",
|
|
454
|
+
message: `Column "${columnName}" is missing`,
|
|
455
|
+
expected: void 0,
|
|
456
|
+
actual: void 0,
|
|
457
|
+
children: []
|
|
458
|
+
});
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const columnChildren = [];
|
|
462
|
+
let columnStatus = "pass";
|
|
463
|
+
const contractNativeType = contractColumn.nativeType;
|
|
464
|
+
const schemaNativeType = schemaColumn.nativeType;
|
|
465
|
+
if (contractNativeType !== schemaNativeType) {
|
|
466
|
+
issues.push({
|
|
467
|
+
kind: "type_mismatch",
|
|
468
|
+
table: tableName,
|
|
469
|
+
column: columnName,
|
|
470
|
+
expected: contractNativeType,
|
|
471
|
+
actual: schemaNativeType,
|
|
472
|
+
message: `Column "${tableName}"."${columnName}" has type mismatch: expected "${contractNativeType}", got "${schemaNativeType}"`
|
|
473
|
+
});
|
|
474
|
+
columnChildren.push({
|
|
475
|
+
status: "fail",
|
|
476
|
+
kind: "type",
|
|
477
|
+
name: "type",
|
|
478
|
+
contractPath: `${columnPath}.nativeType`,
|
|
479
|
+
code: "type_mismatch",
|
|
480
|
+
message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
|
|
481
|
+
expected: contractNativeType,
|
|
482
|
+
actual: schemaNativeType,
|
|
483
|
+
children: []
|
|
484
|
+
});
|
|
485
|
+
columnStatus = "fail";
|
|
486
|
+
}
|
|
487
|
+
if (contractColumn.codecId) {
|
|
488
|
+
const typeMetadata = typeMetadataRegistry.get(contractColumn.codecId);
|
|
489
|
+
if (!typeMetadata) {
|
|
490
|
+
columnChildren.push({
|
|
491
|
+
status: "warn",
|
|
492
|
+
kind: "type",
|
|
493
|
+
name: "type_metadata_missing",
|
|
494
|
+
contractPath: `${columnPath}.codecId`,
|
|
495
|
+
code: "type_metadata_missing",
|
|
496
|
+
message: `codecId "${contractColumn.codecId}" not found in type metadata registry`,
|
|
497
|
+
expected: contractColumn.codecId,
|
|
498
|
+
actual: void 0,
|
|
499
|
+
children: []
|
|
500
|
+
});
|
|
501
|
+
} else if (typeMetadata.nativeType && typeMetadata.nativeType !== contractNativeType) {
|
|
502
|
+
columnChildren.push({
|
|
503
|
+
status: "warn",
|
|
504
|
+
kind: "type",
|
|
505
|
+
name: "type_consistency",
|
|
506
|
+
contractPath: `${columnPath}.codecId`,
|
|
507
|
+
code: "type_consistency_warning",
|
|
508
|
+
message: `codecId "${contractColumn.codecId}" maps to nativeType "${typeMetadata.nativeType}" in registry, but contract has "${contractNativeType}"`,
|
|
509
|
+
expected: typeMetadata.nativeType,
|
|
510
|
+
actual: contractNativeType,
|
|
511
|
+
children: []
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (contractColumn.nullable !== schemaColumn.nullable) {
|
|
516
|
+
issues.push({
|
|
517
|
+
kind: "nullability_mismatch",
|
|
518
|
+
table: tableName,
|
|
519
|
+
column: columnName,
|
|
520
|
+
expected: String(contractColumn.nullable),
|
|
521
|
+
actual: String(schemaColumn.nullable),
|
|
522
|
+
message: `Column "${tableName}"."${columnName}" has nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`
|
|
523
|
+
});
|
|
524
|
+
columnChildren.push({
|
|
525
|
+
status: "fail",
|
|
526
|
+
kind: "nullability",
|
|
527
|
+
name: "nullability",
|
|
528
|
+
contractPath: `${columnPath}.nullable`,
|
|
529
|
+
code: "nullability_mismatch",
|
|
530
|
+
message: `Nullability mismatch: expected ${contractColumn.nullable ? "nullable" : "not null"}, got ${schemaColumn.nullable ? "nullable" : "not null"}`,
|
|
531
|
+
expected: contractColumn.nullable,
|
|
532
|
+
actual: schemaColumn.nullable,
|
|
533
|
+
children: []
|
|
534
|
+
});
|
|
535
|
+
columnStatus = "fail";
|
|
536
|
+
}
|
|
537
|
+
const computedColumnStatus = columnChildren.some((c) => c.status === "fail") ? "fail" : columnChildren.some((c) => c.status === "warn") ? "warn" : "pass";
|
|
538
|
+
const finalColumnStatus = columnChildren.length > 0 ? computedColumnStatus : columnStatus;
|
|
539
|
+
const nullableText = contractColumn.nullable ? "nullable" : "not nullable";
|
|
540
|
+
const columnTypeDisplay = contractColumn.codecId ? `${contractNativeType} (${contractColumn.codecId})` : contractNativeType;
|
|
541
|
+
const failureMessages = columnChildren.filter((child) => child.status === "fail" && child.message).map((child) => child.message).filter((msg) => typeof msg === "string" && msg.length > 0);
|
|
542
|
+
const columnMessage = finalColumnStatus === "fail" && failureMessages.length > 0 ? failureMessages.join("; ") : "";
|
|
543
|
+
const columnCode = (finalColumnStatus === "fail" || finalColumnStatus === "warn") && columnChildren[0] ? columnChildren[0].code : "";
|
|
544
|
+
columnNodes.push({
|
|
545
|
+
status: finalColumnStatus,
|
|
546
|
+
kind: "column",
|
|
547
|
+
name: `${columnName}: ${columnTypeDisplay} (${nullableText})`,
|
|
548
|
+
contractPath: columnPath,
|
|
549
|
+
code: columnCode,
|
|
550
|
+
message: columnMessage,
|
|
551
|
+
expected: void 0,
|
|
552
|
+
actual: void 0,
|
|
553
|
+
children: columnChildren
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
if (columnNodes.length > 0) {
|
|
557
|
+
const columnsStatus = columnNodes.some((c) => c.status === "fail") ? "fail" : columnNodes.some((c) => c.status === "warn") ? "warn" : "pass";
|
|
558
|
+
tableChildren.push({
|
|
559
|
+
status: columnsStatus,
|
|
560
|
+
kind: "columns",
|
|
561
|
+
name: "columns",
|
|
562
|
+
contractPath: `${tablePath}.columns`,
|
|
563
|
+
code: "",
|
|
564
|
+
message: "",
|
|
565
|
+
expected: void 0,
|
|
566
|
+
actual: void 0,
|
|
567
|
+
children: columnNodes
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
if (strict) {
|
|
571
|
+
for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) {
|
|
572
|
+
if (!contractTable.columns[columnName]) {
|
|
573
|
+
issues.push({
|
|
574
|
+
kind: "extra_column",
|
|
575
|
+
table: tableName,
|
|
576
|
+
column: columnName,
|
|
577
|
+
message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`
|
|
578
|
+
});
|
|
579
|
+
columnNodes.push({
|
|
580
|
+
status: "fail",
|
|
581
|
+
kind: "column",
|
|
582
|
+
name: `${columnName}: extra`,
|
|
583
|
+
contractPath: `${tablePath}.columns.${columnName}`,
|
|
584
|
+
code: "extra_column",
|
|
585
|
+
message: `Extra column "${columnName}" found`,
|
|
586
|
+
expected: void 0,
|
|
587
|
+
actual: nativeType,
|
|
588
|
+
children: []
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (contractTable.primaryKey) {
|
|
594
|
+
const pkStatus = verifyPrimaryKey(
|
|
595
|
+
contractTable.primaryKey,
|
|
596
|
+
schemaTable.primaryKey,
|
|
597
|
+
tableName,
|
|
598
|
+
issues
|
|
599
|
+
);
|
|
600
|
+
if (pkStatus === "fail") {
|
|
601
|
+
tableChildren.push({
|
|
602
|
+
status: "fail",
|
|
603
|
+
kind: "primaryKey",
|
|
604
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
605
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
606
|
+
code: "primary_key_mismatch",
|
|
607
|
+
message: "Primary key mismatch",
|
|
608
|
+
expected: contractTable.primaryKey,
|
|
609
|
+
actual: schemaTable.primaryKey,
|
|
610
|
+
children: []
|
|
611
|
+
});
|
|
612
|
+
} else {
|
|
613
|
+
tableChildren.push({
|
|
614
|
+
status: "pass",
|
|
615
|
+
kind: "primaryKey",
|
|
616
|
+
name: `primary key: ${contractTable.primaryKey.columns.join(", ")}`,
|
|
617
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
618
|
+
code: "",
|
|
619
|
+
message: "",
|
|
620
|
+
expected: void 0,
|
|
621
|
+
actual: void 0,
|
|
622
|
+
children: []
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
} else if (schemaTable.primaryKey && strict) {
|
|
626
|
+
issues.push({
|
|
627
|
+
kind: "extra_primary_key",
|
|
628
|
+
table: tableName,
|
|
629
|
+
message: "Extra primary key found in database (not in contract)"
|
|
630
|
+
});
|
|
631
|
+
tableChildren.push({
|
|
632
|
+
status: "fail",
|
|
633
|
+
kind: "primaryKey",
|
|
634
|
+
name: `primary key: ${schemaTable.primaryKey.columns.join(", ")}`,
|
|
635
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
636
|
+
code: "extra_primary_key",
|
|
637
|
+
message: "Extra primary key found",
|
|
638
|
+
expected: void 0,
|
|
639
|
+
actual: schemaTable.primaryKey,
|
|
640
|
+
children: []
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
const fkStatuses = verifyForeignKeys(
|
|
644
|
+
contractTable.foreignKeys,
|
|
645
|
+
schemaTable.foreignKeys,
|
|
646
|
+
tableName,
|
|
647
|
+
tablePath,
|
|
648
|
+
issues,
|
|
649
|
+
strict
|
|
650
|
+
);
|
|
651
|
+
tableChildren.push(...fkStatuses);
|
|
652
|
+
const uniqueStatuses = verifyUniqueConstraints(
|
|
653
|
+
contractTable.uniques,
|
|
654
|
+
schemaTable.uniques,
|
|
655
|
+
tableName,
|
|
656
|
+
tablePath,
|
|
657
|
+
issues,
|
|
658
|
+
strict
|
|
659
|
+
);
|
|
660
|
+
tableChildren.push(...uniqueStatuses);
|
|
661
|
+
const indexStatuses = verifyIndexes(
|
|
662
|
+
contractTable.indexes,
|
|
663
|
+
schemaTable.indexes,
|
|
664
|
+
tableName,
|
|
665
|
+
tablePath,
|
|
666
|
+
issues,
|
|
667
|
+
strict
|
|
668
|
+
);
|
|
669
|
+
tableChildren.push(...indexStatuses);
|
|
670
|
+
const tableStatus = tableChildren.some((c) => c.status === "fail") ? "fail" : tableChildren.some((c) => c.status === "warn") ? "warn" : "pass";
|
|
671
|
+
const tableFailureMessages = tableChildren.filter((child) => child.status === "fail" && child.message).map((child) => child.message).filter((msg) => typeof msg === "string" && msg.length > 0);
|
|
672
|
+
const tableMessage = tableStatus === "fail" && tableFailureMessages.length > 0 ? `${tableFailureMessages.length} issue${tableFailureMessages.length === 1 ? "" : "s"}` : "";
|
|
673
|
+
const tableCode = tableStatus === "fail" && tableChildren.length > 0 && tableChildren[0] ? tableChildren[0].code : "";
|
|
674
|
+
rootChildren.push({
|
|
675
|
+
status: tableStatus,
|
|
676
|
+
kind: "table",
|
|
677
|
+
name: `table ${tableName}`,
|
|
678
|
+
contractPath: tablePath,
|
|
679
|
+
code: tableCode,
|
|
680
|
+
message: tableMessage,
|
|
681
|
+
expected: void 0,
|
|
682
|
+
actual: void 0,
|
|
683
|
+
children: tableChildren
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
if (strict) {
|
|
687
|
+
for (const tableName of Object.keys(schemaTables)) {
|
|
688
|
+
if (!contractTables[tableName]) {
|
|
689
|
+
issues.push({
|
|
690
|
+
kind: "extra_table",
|
|
691
|
+
table: tableName,
|
|
692
|
+
message: `Extra table "${tableName}" found in database (not in contract)`
|
|
693
|
+
});
|
|
694
|
+
rootChildren.push({
|
|
695
|
+
status: "fail",
|
|
696
|
+
kind: "table",
|
|
697
|
+
name: `table ${tableName}`,
|
|
698
|
+
contractPath: `storage.tables.${tableName}`,
|
|
699
|
+
code: "extra_table",
|
|
700
|
+
message: `Extra table "${tableName}" found`,
|
|
701
|
+
expected: void 0,
|
|
702
|
+
actual: void 0,
|
|
703
|
+
children: []
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const extensionStatuses = verifyExtensions(
|
|
709
|
+
contract.extensions,
|
|
710
|
+
schema.extensions,
|
|
711
|
+
contractTarget,
|
|
712
|
+
issues,
|
|
713
|
+
strict
|
|
714
|
+
);
|
|
715
|
+
rootChildren.push(...extensionStatuses);
|
|
716
|
+
const rootStatus = rootChildren.some((c) => c.status === "fail") ? "fail" : rootChildren.some((c) => c.status === "warn") ? "warn" : "pass";
|
|
717
|
+
const root = {
|
|
718
|
+
status: rootStatus,
|
|
719
|
+
kind: "contract",
|
|
720
|
+
name: "contract",
|
|
721
|
+
contractPath: "",
|
|
722
|
+
code: "",
|
|
723
|
+
message: "",
|
|
724
|
+
expected: void 0,
|
|
725
|
+
actual: void 0,
|
|
726
|
+
children: rootChildren
|
|
727
|
+
};
|
|
728
|
+
const counts = computeCounts(root);
|
|
729
|
+
const ok = counts.fail === 0;
|
|
730
|
+
const code = ok ? void 0 : "PN-SCHEMA-0001";
|
|
731
|
+
const summary = ok ? "Database schema satisfies contract" : `Database schema does not satisfy contract (${counts.fail} failure${counts.fail === 1 ? "" : "s"})`;
|
|
732
|
+
const totalTime = Date.now() - startTime;
|
|
733
|
+
return {
|
|
734
|
+
ok,
|
|
735
|
+
...ifDefined("code", code),
|
|
736
|
+
summary,
|
|
737
|
+
contract: {
|
|
738
|
+
coreHash: contractCoreHash,
|
|
739
|
+
...ifDefined("profileHash", contractProfileHash)
|
|
740
|
+
},
|
|
741
|
+
target: {
|
|
742
|
+
expected: contractTarget,
|
|
743
|
+
actual: contractTarget
|
|
744
|
+
},
|
|
745
|
+
schema: {
|
|
746
|
+
issues,
|
|
747
|
+
root,
|
|
748
|
+
counts
|
|
749
|
+
},
|
|
750
|
+
meta: {
|
|
751
|
+
strict,
|
|
752
|
+
...ifDefined("contractPath", context?.contractPath),
|
|
753
|
+
...ifDefined("configPath", context?.configPath)
|
|
754
|
+
},
|
|
755
|
+
timings: {
|
|
756
|
+
total: totalTime
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export {
|
|
762
|
+
verifySqlSchema
|
|
763
|
+
};
|
|
764
|
+
//# sourceMappingURL=chunk-I7QWTBWS.js.map
|