@prisma-next/family-sql 0.12.0-dev.3 → 0.12.0-dev.30
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-CKCfVhTd.d.mts +133 -0
- package/dist/control-adapter-CKCfVhTd.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 +201 -16
- 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-B084B5kI.d.mts} +16 -6
- package/dist/types-B084B5kI.d.mts.map +1 -0
- package/dist/{verify-sql-schema-CN7pPoTC.d.mts → verify-sql-schema-Cj3c2t5Z.d.mts} +8 -2
- package/dist/verify-sql-schema-Cj3c2t5Z.d.mts.map +1 -0
- package/dist/{verify-sql-schema-CYLsGCFO.mjs → verify-sql-schema-fljAiO-C.mjs} +403 -310
- package/dist/verify-sql-schema-fljAiO-C.mjs.map +1 -0
- package/package.json +21 -21
- package/src/core/control-adapter.ts +43 -3
- package/src/core/control-instance.ts +40 -41
- package/src/core/default-namespace.ts +9 -0
- package/src/core/migrations/control-policy.ts +322 -0
- package/src/core/migrations/plan-helpers.ts +16 -0
- package/src/core/migrations/types.ts +11 -2
- package/src/core/schema-verify/control-verify-emit.ts +46 -0
- package/src/core/schema-verify/verifier-disposition.ts +53 -0
- package/src/core/schema-verify/verify-helpers.ts +151 -110
- package/src/core/schema-verify/verify-sql-schema.ts +291 -155
- 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/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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* These functions verify schema IR against contract requirements.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { ControlPolicy } from '@prisma-next/contract/types';
|
|
6
7
|
import type {
|
|
7
8
|
SchemaIssue,
|
|
8
9
|
SchemaVerificationNode,
|
|
@@ -15,6 +16,10 @@ import type {
|
|
|
15
16
|
UniqueConstraint,
|
|
16
17
|
} from '@prisma-next/sql-contract/types';
|
|
17
18
|
import type { SqlForeignKeyIR, SqlIndexIR, SqlUniqueIR } from '@prisma-next/sql-schema-ir/types';
|
|
19
|
+
import {
|
|
20
|
+
emitIssueAndNodeUnderControlPolicy,
|
|
21
|
+
emitIssueUnderControlPolicy,
|
|
22
|
+
} from './control-verify-emit';
|
|
18
23
|
|
|
19
24
|
function indexOptionsLooselyEqual(
|
|
20
25
|
a: Record<string, unknown> | undefined,
|
|
@@ -135,34 +140,34 @@ export function verifyPrimaryKey(
|
|
|
135
140
|
schemaPK: PrimaryKey | undefined,
|
|
136
141
|
tableName: string,
|
|
137
142
|
namespaceId: string,
|
|
143
|
+
tableControlPolicy: ControlPolicy,
|
|
138
144
|
issues: SchemaIssue[],
|
|
139
|
-
): 'pass' | 'fail' {
|
|
145
|
+
): 'pass' | 'warn' | 'fail' {
|
|
140
146
|
if (!schemaPK) {
|
|
141
|
-
|
|
147
|
+
const issue: SchemaIssue = {
|
|
142
148
|
kind: 'primary_key_mismatch',
|
|
143
149
|
table: tableName,
|
|
144
150
|
namespaceId,
|
|
145
151
|
expected: contractPK.columns.join(', '),
|
|
146
152
|
message: `Table "${tableName}" is missing primary key`,
|
|
147
|
-
}
|
|
148
|
-
|
|
153
|
+
};
|
|
154
|
+
const outcome = emitIssueUnderControlPolicy(tableControlPolicy, issue, issues);
|
|
155
|
+
return outcome === 'suppress' ? 'pass' : outcome;
|
|
149
156
|
}
|
|
150
157
|
|
|
151
158
|
if (!arraysEqual(contractPK.columns, schemaPK.columns)) {
|
|
152
|
-
|
|
159
|
+
const issue: SchemaIssue = {
|
|
153
160
|
kind: 'primary_key_mismatch',
|
|
154
161
|
table: tableName,
|
|
155
162
|
namespaceId,
|
|
156
163
|
expected: contractPK.columns.join(', '),
|
|
157
164
|
actual: schemaPK.columns.join(', '),
|
|
158
165
|
message: `Table "${tableName}" has primary key mismatch: expected columns [${contractPK.columns.join(', ')}], got [${schemaPK.columns.join(', ')}]`,
|
|
159
|
-
}
|
|
160
|
-
|
|
166
|
+
};
|
|
167
|
+
const outcome = emitIssueUnderControlPolicy(tableControlPolicy, issue, issues);
|
|
168
|
+
return outcome === 'suppress' ? 'pass' : outcome;
|
|
161
169
|
}
|
|
162
170
|
|
|
163
|
-
// Name differences are ignored for semantic satisfaction.
|
|
164
|
-
// Names are persisted for deterministic DDL and diagnostics but are not identity.
|
|
165
|
-
|
|
166
171
|
return 'pass';
|
|
167
172
|
}
|
|
168
173
|
|
|
@@ -179,6 +184,7 @@ export function verifyForeignKeys(
|
|
|
179
184
|
tableName: string,
|
|
180
185
|
namespaceId: string,
|
|
181
186
|
tablePath: string,
|
|
187
|
+
tableControlPolicy: ControlPolicy,
|
|
182
188
|
issues: SchemaIssue[],
|
|
183
189
|
strict: boolean,
|
|
184
190
|
): SchemaVerificationNode[] {
|
|
@@ -207,52 +213,62 @@ export function verifyForeignKeys(
|
|
|
207
213
|
});
|
|
208
214
|
|
|
209
215
|
if (!matchingFK) {
|
|
210
|
-
|
|
216
|
+
const issue: SchemaIssue = {
|
|
211
217
|
kind: 'foreign_key_mismatch',
|
|
212
218
|
table: tableName,
|
|
213
219
|
namespaceId,
|
|
214
220
|
expected: `${contractFK.source.columns.join(', ')} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(', ')})`,
|
|
215
221
|
message: `Table "${tableName}" is missing foreign key: ${contractFK.source.columns.join(', ')} -> ${contractFK.target.tableName}(${contractFK.target.columns.join(', ')})`,
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
222
|
+
};
|
|
223
|
+
emitIssueAndNodeUnderControlPolicy(
|
|
224
|
+
tableControlPolicy,
|
|
225
|
+
issue,
|
|
226
|
+
{
|
|
227
|
+
status: 'fail',
|
|
228
|
+
kind: 'foreignKey',
|
|
229
|
+
name: `foreignKey(${contractFK.source.columns.join(', ')})`,
|
|
230
|
+
contractPath: fkPath,
|
|
231
|
+
code: 'foreign_key_mismatch',
|
|
232
|
+
message: 'Foreign key missing',
|
|
233
|
+
expected: contractFK,
|
|
234
|
+
actual: undefined,
|
|
235
|
+
children: [],
|
|
236
|
+
},
|
|
237
|
+
issues,
|
|
238
|
+
nodes,
|
|
239
|
+
);
|
|
228
240
|
} else {
|
|
229
241
|
const actionMismatches = getReferentialActionMismatches(contractFK, matchingFK);
|
|
230
242
|
if (actionMismatches.length > 0) {
|
|
231
243
|
const combinedMessage = actionMismatches.map((m) => m.message).join('; ');
|
|
232
244
|
const combinedExpected = actionMismatches.map((m) => m.expected).join(', ');
|
|
233
245
|
const combinedActual = actionMismatches.map((m) => m.actual).join(', ');
|
|
234
|
-
|
|
246
|
+
const issue: SchemaIssue = {
|
|
235
247
|
kind: 'foreign_key_mismatch',
|
|
236
248
|
table: tableName,
|
|
237
249
|
namespaceId,
|
|
238
|
-
// Set indexOrConstraint so the planner classifies this as a non-additive
|
|
239
|
-
// conflict (existing FK with wrong actions cannot be fixed additively).
|
|
240
250
|
indexOrConstraint: matchingFK.name ?? `fk(${contractFK.source.columns.join(',')})`,
|
|
241
251
|
expected: combinedExpected,
|
|
242
252
|
actual: combinedActual,
|
|
243
253
|
message: `Table "${tableName}" foreign key ${contractFK.source.columns.join(', ')} -> ${contractFK.target.tableName}: ${combinedMessage}`,
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
254
|
+
};
|
|
255
|
+
emitIssueAndNodeUnderControlPolicy(
|
|
256
|
+
tableControlPolicy,
|
|
257
|
+
issue,
|
|
258
|
+
{
|
|
259
|
+
status: 'fail',
|
|
260
|
+
kind: 'foreignKey',
|
|
261
|
+
name: `foreignKey(${contractFK.source.columns.join(', ')})`,
|
|
262
|
+
contractPath: fkPath,
|
|
263
|
+
code: 'foreign_key_mismatch',
|
|
264
|
+
message: combinedMessage,
|
|
265
|
+
expected: contractFK,
|
|
266
|
+
actual: matchingFK,
|
|
267
|
+
children: [],
|
|
268
|
+
},
|
|
269
|
+
issues,
|
|
270
|
+
nodes,
|
|
271
|
+
);
|
|
256
272
|
} else {
|
|
257
273
|
nodes.push({
|
|
258
274
|
status: 'pass',
|
|
@@ -286,24 +302,30 @@ export function verifyForeignKeys(
|
|
|
286
302
|
});
|
|
287
303
|
|
|
288
304
|
if (!matchingFK) {
|
|
289
|
-
|
|
305
|
+
const issue: SchemaIssue = {
|
|
290
306
|
kind: 'extra_foreign_key',
|
|
291
307
|
table: tableName,
|
|
292
308
|
namespaceId,
|
|
293
309
|
indexOrConstraint: schemaFK.name ?? `fk(${schemaFK.columns.join(',')})`,
|
|
294
310
|
message: `Extra foreign key found in database (not in contract): ${schemaFK.columns.join(', ')} -> ${schemaFK.referencedTable}(${schemaFK.referencedColumns.join(', ')})`,
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
311
|
+
};
|
|
312
|
+
emitIssueAndNodeUnderControlPolicy(
|
|
313
|
+
tableControlPolicy,
|
|
314
|
+
issue,
|
|
315
|
+
{
|
|
316
|
+
status: 'fail',
|
|
317
|
+
kind: 'foreignKey',
|
|
318
|
+
name: `foreignKey(${schemaFK.columns.join(', ')})`,
|
|
319
|
+
contractPath: `${tablePath}.foreignKeys[${schemaFK.columns.join(',')}]`,
|
|
320
|
+
code: 'extra_foreign_key',
|
|
321
|
+
message: 'Extra foreign key found',
|
|
322
|
+
expected: undefined,
|
|
323
|
+
actual: schemaFK,
|
|
324
|
+
children: [],
|
|
325
|
+
},
|
|
326
|
+
issues,
|
|
327
|
+
nodes,
|
|
328
|
+
);
|
|
307
329
|
}
|
|
308
330
|
}
|
|
309
331
|
}
|
|
@@ -329,6 +351,7 @@ export function verifyUniqueConstraints(
|
|
|
329
351
|
tableName: string,
|
|
330
352
|
namespaceId: string,
|
|
331
353
|
tablePath: string,
|
|
354
|
+
tableControlPolicy: ControlPolicy,
|
|
332
355
|
issues: SchemaIssue[],
|
|
333
356
|
strict: boolean,
|
|
334
357
|
): SchemaVerificationNode[] {
|
|
@@ -349,27 +372,31 @@ export function verifyUniqueConstraints(
|
|
|
349
372
|
schemaIndexes.find((idx) => idx.unique && arraysEqual(idx.columns, contractUnique.columns));
|
|
350
373
|
|
|
351
374
|
if (!matchingUnique && !matchingUniqueIndex) {
|
|
352
|
-
|
|
375
|
+
const issue: SchemaIssue = {
|
|
353
376
|
kind: 'unique_constraint_mismatch',
|
|
354
377
|
table: tableName,
|
|
355
378
|
namespaceId,
|
|
356
379
|
expected: contractUnique.columns.join(', '),
|
|
357
380
|
message: `Table "${tableName}" is missing unique constraint: ${contractUnique.columns.join(', ')}`,
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
381
|
+
};
|
|
382
|
+
emitIssueAndNodeUnderControlPolicy(
|
|
383
|
+
tableControlPolicy,
|
|
384
|
+
issue,
|
|
385
|
+
{
|
|
386
|
+
status: 'fail',
|
|
387
|
+
kind: 'unique',
|
|
388
|
+
name: `unique(${contractUnique.columns.join(', ')})`,
|
|
389
|
+
contractPath: uniquePath,
|
|
390
|
+
code: 'unique_constraint_mismatch',
|
|
391
|
+
message: 'Unique constraint missing',
|
|
392
|
+
expected: contractUnique,
|
|
393
|
+
actual: undefined,
|
|
394
|
+
children: [],
|
|
395
|
+
},
|
|
396
|
+
issues,
|
|
397
|
+
nodes,
|
|
398
|
+
);
|
|
370
399
|
} else {
|
|
371
|
-
// Name differences are ignored for semantic satisfaction.
|
|
372
|
-
// Names are persisted for deterministic DDL and diagnostics but are not identity.
|
|
373
400
|
nodes.push({
|
|
374
401
|
status: 'pass',
|
|
375
402
|
kind: 'unique',
|
|
@@ -384,7 +411,6 @@ export function verifyUniqueConstraints(
|
|
|
384
411
|
}
|
|
385
412
|
}
|
|
386
413
|
|
|
387
|
-
// Check for extra uniques in strict mode
|
|
388
414
|
if (strict) {
|
|
389
415
|
for (const schemaUnique of schemaUniques) {
|
|
390
416
|
const matchingUnique = contractUniques.find((u) =>
|
|
@@ -392,24 +418,30 @@ export function verifyUniqueConstraints(
|
|
|
392
418
|
);
|
|
393
419
|
|
|
394
420
|
if (!matchingUnique) {
|
|
395
|
-
|
|
421
|
+
const issue: SchemaIssue = {
|
|
396
422
|
kind: 'extra_unique_constraint',
|
|
397
423
|
table: tableName,
|
|
398
424
|
namespaceId,
|
|
399
425
|
indexOrConstraint: schemaUnique.name ?? `unique(${schemaUnique.columns.join(',')})`,
|
|
400
426
|
message: `Extra unique constraint found in database (not in contract): ${schemaUnique.columns.join(', ')}`,
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
427
|
+
};
|
|
428
|
+
emitIssueAndNodeUnderControlPolicy(
|
|
429
|
+
tableControlPolicy,
|
|
430
|
+
issue,
|
|
431
|
+
{
|
|
432
|
+
status: 'fail',
|
|
433
|
+
kind: 'unique',
|
|
434
|
+
name: `unique(${schemaUnique.columns.join(', ')})`,
|
|
435
|
+
contractPath: `${tablePath}.uniques[${schemaUnique.columns.join(',')}]`,
|
|
436
|
+
code: 'extra_unique_constraint',
|
|
437
|
+
message: 'Extra unique constraint found',
|
|
438
|
+
expected: undefined,
|
|
439
|
+
actual: schemaUnique,
|
|
440
|
+
children: [],
|
|
441
|
+
},
|
|
442
|
+
issues,
|
|
443
|
+
nodes,
|
|
444
|
+
);
|
|
413
445
|
}
|
|
414
446
|
}
|
|
415
447
|
}
|
|
@@ -435,6 +467,7 @@ export function verifyIndexes(
|
|
|
435
467
|
tableName: string,
|
|
436
468
|
namespaceId: string,
|
|
437
469
|
tablePath: string,
|
|
470
|
+
tableControlPolicy: ControlPolicy,
|
|
438
471
|
issues: SchemaIssue[],
|
|
439
472
|
strict: boolean,
|
|
440
473
|
): SchemaVerificationNode[] {
|
|
@@ -461,27 +494,31 @@ export function verifyIndexes(
|
|
|
461
494
|
schemaUniques.find((u) => arraysEqual(u.columns, contractIndex.columns));
|
|
462
495
|
|
|
463
496
|
if (!matchingIndex && !matchingUniqueConstraint) {
|
|
464
|
-
|
|
497
|
+
const issue: SchemaIssue = {
|
|
465
498
|
kind: 'index_mismatch',
|
|
466
499
|
table: tableName,
|
|
467
500
|
namespaceId,
|
|
468
501
|
expected: contractIndex.columns.join(', '),
|
|
469
502
|
message: `Table "${tableName}" is missing index: ${contractIndex.columns.join(', ')}`,
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
503
|
+
};
|
|
504
|
+
emitIssueAndNodeUnderControlPolicy(
|
|
505
|
+
tableControlPolicy,
|
|
506
|
+
issue,
|
|
507
|
+
{
|
|
508
|
+
status: 'fail',
|
|
509
|
+
kind: 'index',
|
|
510
|
+
name: `index(${contractIndex.columns.join(', ')})`,
|
|
511
|
+
contractPath: indexPath,
|
|
512
|
+
code: 'index_mismatch',
|
|
513
|
+
message: 'Index missing',
|
|
514
|
+
expected: contractIndex,
|
|
515
|
+
actual: undefined,
|
|
516
|
+
children: [],
|
|
517
|
+
},
|
|
518
|
+
issues,
|
|
519
|
+
nodes,
|
|
520
|
+
);
|
|
482
521
|
} else {
|
|
483
|
-
// Name differences are ignored for semantic satisfaction.
|
|
484
|
-
// Names are persisted for deterministic DDL and diagnostics but are not identity.
|
|
485
522
|
nodes.push({
|
|
486
523
|
status: 'pass',
|
|
487
524
|
kind: 'index',
|
|
@@ -496,10 +533,8 @@ export function verifyIndexes(
|
|
|
496
533
|
}
|
|
497
534
|
}
|
|
498
535
|
|
|
499
|
-
// Check for extra indexes in strict mode
|
|
500
536
|
if (strict) {
|
|
501
537
|
for (const schemaIndex of schemaIndexes) {
|
|
502
|
-
// Skip unique indexes (they're handled as unique constraints)
|
|
503
538
|
if (schemaIndex.unique) {
|
|
504
539
|
continue;
|
|
505
540
|
}
|
|
@@ -510,24 +545,30 @@ export function verifyIndexes(
|
|
|
510
545
|
);
|
|
511
546
|
|
|
512
547
|
if (!matchingIndex) {
|
|
513
|
-
|
|
548
|
+
const issue: SchemaIssue = {
|
|
514
549
|
kind: 'extra_index',
|
|
515
550
|
table: tableName,
|
|
516
551
|
namespaceId,
|
|
517
552
|
indexOrConstraint: schemaIndex.name ?? `idx(${schemaIndex.columns.join(',')})`,
|
|
518
553
|
message: `Extra index found in database (not in contract): ${schemaIndex.columns.join(', ')}`,
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
554
|
+
};
|
|
555
|
+
emitIssueAndNodeUnderControlPolicy(
|
|
556
|
+
tableControlPolicy,
|
|
557
|
+
issue,
|
|
558
|
+
{
|
|
559
|
+
status: 'fail',
|
|
560
|
+
kind: 'index',
|
|
561
|
+
name: `index(${schemaIndex.columns.join(', ')})`,
|
|
562
|
+
contractPath: `${tablePath}.indexes[${schemaIndex.columns.join(',')}]`,
|
|
563
|
+
code: 'extra_index',
|
|
564
|
+
message: 'Extra index found',
|
|
565
|
+
expected: undefined,
|
|
566
|
+
actual: schemaIndex,
|
|
567
|
+
children: [],
|
|
568
|
+
},
|
|
569
|
+
issues,
|
|
570
|
+
nodes,
|
|
571
|
+
);
|
|
531
572
|
}
|
|
532
573
|
}
|
|
533
574
|
}
|