@prisma-next/family-sql 0.12.0-dev.17 → 0.12.0-dev.2
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.d.mts +109 -2
- package/dist/control-adapter.d.mts.map +1 -0
- package/dist/control.d.mts +2 -2
- package/dist/control.mjs +13 -20
- package/dist/control.mjs.map +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/runtime.d.mts +1 -3
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -3
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema-verify.d.mts +1 -1
- package/dist/schema-verify.d.mts.map +1 -1
- package/dist/schema-verify.mjs +1 -1
- package/dist/{types-BQiqw6kR.d.mts → types-CeeCStqw.d.mts} +5 -14
- package/dist/types-CeeCStqw.d.mts.map +1 -0
- package/dist/{verify-sql-schema-Cj3c2t5Z.d.mts → verify-sql-schema-CN7pPoTC.d.mts} +2 -8
- package/dist/verify-sql-schema-CN7pPoTC.d.mts.map +1 -0
- package/dist/{verify-sql-schema-CXW_D9dW.mjs → verify-sql-schema-CYLsGCFO.mjs} +310 -403
- package/dist/verify-sql-schema-CYLsGCFO.mjs.map +1 -0
- package/package.json +21 -21
- package/src/core/control-adapter.ts +3 -44
- package/src/core/control-instance.ts +41 -40
- package/src/core/migrations/types.ts +1 -8
- package/src/core/schema-verify/verify-helpers.ts +110 -151
- package/src/core/schema-verify/verify-sql-schema.ts +155 -291
- package/src/exports/runtime.ts +0 -7
- package/dist/control-adapter-8tV9WgWK.d.mts +0 -134
- package/dist/control-adapter-8tV9WgWK.d.mts.map +0 -1
- package/dist/types-BQiqw6kR.d.mts.map +0 -1
- package/dist/verify-sql-schema-CXW_D9dW.mjs.map +0 -1
- package/dist/verify-sql-schema-Cj3c2t5Z.d.mts.map +0 -1
- package/src/core/default-namespace.ts +0 -9
- package/src/core/schema-verify/control-verify-emit.ts +0 -46
- package/src/core/schema-verify/verifier-disposition.ts +0 -53
|
@@ -6,14 +6,12 @@
|
|
|
6
6
|
* by migration planners and other tools that need to compare schema states.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { ColumnDefault, Contract
|
|
10
|
-
import { effectiveControlPolicy } from '@prisma-next/contract/types';
|
|
9
|
+
import type { ColumnDefault, Contract } from '@prisma-next/contract/types';
|
|
11
10
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
12
11
|
import type {
|
|
13
12
|
OperationContext,
|
|
14
13
|
SchemaIssue,
|
|
15
14
|
SchemaVerificationNode,
|
|
16
|
-
VerificationStatus,
|
|
17
15
|
VerifyDatabaseSchemaResult,
|
|
18
16
|
} from '@prisma-next/framework-components/control';
|
|
19
17
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
@@ -31,8 +29,6 @@ import { canonicalStringify } from '@prisma-next/utils/canonical-stringify';
|
|
|
31
29
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
32
30
|
import { extractCodecControlHooks } from '../assembly';
|
|
33
31
|
import type { CodecControlHooks } from '../migrations/types';
|
|
34
|
-
import { emitIssueAndNodeUnderControlPolicy } from './control-verify-emit';
|
|
35
|
-
import { verifierDisposition } from './verifier-disposition';
|
|
36
32
|
import {
|
|
37
33
|
arraysEqual,
|
|
38
34
|
computeCounts,
|
|
@@ -42,8 +38,6 @@ import {
|
|
|
42
38
|
verifyUniqueConstraints,
|
|
43
39
|
} from './verify-helpers';
|
|
44
40
|
|
|
45
|
-
export type ColumnsCompatible = (declared: string, live: string) => boolean;
|
|
46
|
-
|
|
47
41
|
/**
|
|
48
42
|
* Function type for normalizing raw database default expressions into ColumnDefault.
|
|
49
43
|
* Target-specific implementations handle database dialect differences.
|
|
@@ -107,11 +101,6 @@ export interface VerifySqlSchemaOptions {
|
|
|
107
101
|
enumType: PostgresEnumStorageEntry,
|
|
108
102
|
namespaceId: string,
|
|
109
103
|
) => readonly string[] | null;
|
|
110
|
-
/**
|
|
111
|
-
* Target-supplied compatible-shape relation for `external` column type checks.
|
|
112
|
-
* Defaults to exact string equality when omitted.
|
|
113
|
-
*/
|
|
114
|
-
readonly columnsCompatible?: ColumnsCompatible;
|
|
115
104
|
}
|
|
116
105
|
|
|
117
106
|
/**
|
|
@@ -134,7 +123,6 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
134
123
|
normalizeDefault,
|
|
135
124
|
normalizeNativeType,
|
|
136
125
|
resolveExistingEnumValues,
|
|
137
|
-
columnsCompatible,
|
|
138
126
|
} = options;
|
|
139
127
|
const startTime = Date.now();
|
|
140
128
|
|
|
@@ -171,7 +159,6 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
171
159
|
storageTypes,
|
|
172
160
|
...ifDefined('normalizeDefault', normalizeDefault),
|
|
173
161
|
...ifDefined('normalizeNativeType', normalizeNativeType),
|
|
174
|
-
...ifDefined('columnsCompatible', columnsCompatible),
|
|
175
162
|
});
|
|
176
163
|
|
|
177
164
|
validateFrameworkComponentsForExtensions(contract, options.frameworkComponents);
|
|
@@ -184,39 +171,24 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
184
171
|
// namespace coordinate — a bare-name aggregation would collapse them
|
|
185
172
|
// (last-write-wins) and verify only one.
|
|
186
173
|
const typeNodes: SchemaVerificationNode[] = [];
|
|
187
|
-
// Storage-type findings dispatch through the same control policy as tables
|
|
188
|
-
// and columns: each issue's disposition (fail / warn / suppress) is resolved
|
|
189
|
-
// from the type's effective control so an `external`/`observed` enum no longer
|
|
190
|
-
// hard-fails on value drift. `suppress` drops the issue entirely; the node
|
|
191
|
-
// status is the worst surviving disposition.
|
|
192
174
|
const pushTypeNode = (
|
|
193
175
|
typeName: string,
|
|
194
176
|
contractPath: string,
|
|
195
177
|
typeIssues: readonly SchemaIssue[],
|
|
196
|
-
controlPolicy: ControlPolicy,
|
|
197
178
|
): void => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
let emitted = 0;
|
|
201
|
-
for (const issue of typeIssues) {
|
|
202
|
-
const disposition = verifierDisposition(controlPolicy, issue.kind);
|
|
203
|
-
if (disposition === 'suppress') continue;
|
|
204
|
-
issues.push(issue);
|
|
205
|
-
emitted += 1;
|
|
206
|
-
if (code === '') code = issue.kind;
|
|
207
|
-
if (disposition === 'fail') {
|
|
208
|
-
status = 'fail';
|
|
209
|
-
} else if (disposition === 'warn' && status !== 'fail') {
|
|
210
|
-
status = 'warn';
|
|
211
|
-
}
|
|
179
|
+
if (typeIssues.length > 0) {
|
|
180
|
+
issues.push(...typeIssues);
|
|
212
181
|
}
|
|
213
182
|
typeNodes.push({
|
|
214
|
-
status,
|
|
183
|
+
status: typeIssues.length > 0 ? 'fail' : 'pass',
|
|
215
184
|
kind: 'storageType',
|
|
216
185
|
name: `type ${typeName}`,
|
|
217
186
|
contractPath,
|
|
218
|
-
code:
|
|
219
|
-
message:
|
|
187
|
+
code: typeIssues.length > 0 ? (typeIssues[0]?.kind ?? '') : '',
|
|
188
|
+
message:
|
|
189
|
+
typeIssues.length > 0
|
|
190
|
+
? `${typeIssues.length} issue${typeIssues.length === 1 ? '' : 's'}`
|
|
191
|
+
: '',
|
|
220
192
|
expected: undefined,
|
|
221
193
|
actual: undefined,
|
|
222
194
|
children: [],
|
|
@@ -237,7 +209,6 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
237
209
|
resolveExistingEnumValues,
|
|
238
210
|
namespaceId: UNBOUND_NAMESPACE_ID,
|
|
239
211
|
}),
|
|
240
|
-
effectiveControlPolicy(typeInstance.control, contract.defaultControl),
|
|
241
212
|
);
|
|
242
213
|
} else if (isStorageTypeInstance(typeInstance)) {
|
|
243
214
|
const hook = codecHooks.get(typeInstance.codecId);
|
|
@@ -245,7 +216,6 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
245
216
|
typeName,
|
|
246
217
|
`storage.types.${typeName}`,
|
|
247
218
|
hook?.verifyType ? hook.verifyType({ typeName, typeInstance, schema }) : [],
|
|
248
|
-
effectiveControlPolicy(undefined, contract.defaultControl),
|
|
249
219
|
);
|
|
250
220
|
}
|
|
251
221
|
}
|
|
@@ -268,17 +238,12 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
268
238
|
resolveExistingEnumValues,
|
|
269
239
|
namespaceId: nsId,
|
|
270
240
|
}),
|
|
271
|
-
effectiveControlPolicy(entry.control, contract.defaultControl),
|
|
272
241
|
);
|
|
273
242
|
}
|
|
274
243
|
}
|
|
275
244
|
|
|
276
245
|
if (typeNodes.length > 0) {
|
|
277
|
-
const typesStatus
|
|
278
|
-
? 'fail'
|
|
279
|
-
: typeNodes.some((n) => n.status === 'warn')
|
|
280
|
-
? 'warn'
|
|
281
|
-
: 'pass';
|
|
246
|
+
const typesStatus = typeNodes.some((n) => n.status === 'fail') ? 'fail' : 'pass';
|
|
282
247
|
rootChildren.push({
|
|
283
248
|
status: typesStatus,
|
|
284
249
|
kind: 'storageTypes',
|
|
@@ -338,6 +303,8 @@ export function verifySqlSchema(options: VerifySqlSchemaOptions): VerifyDatabase
|
|
|
338
303
|
};
|
|
339
304
|
}
|
|
340
305
|
|
|
306
|
+
type VerificationStatus = 'pass' | 'warn' | 'fail';
|
|
307
|
+
|
|
341
308
|
/**
|
|
342
309
|
* Native verification walk for `PostgresEnumStorageEntry` instances (no codec hook).
|
|
343
310
|
*
|
|
@@ -419,7 +386,6 @@ function verifySchemaTables(options: {
|
|
|
419
386
|
storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>;
|
|
420
387
|
normalizeDefault?: DefaultNormalizer;
|
|
421
388
|
normalizeNativeType?: NativeTypeNormalizer;
|
|
422
|
-
columnsCompatible?: ColumnsCompatible;
|
|
423
389
|
}): { issues: SchemaIssue[]; rootChildren: SchemaVerificationNode[] } {
|
|
424
390
|
const {
|
|
425
391
|
contract,
|
|
@@ -430,9 +396,7 @@ function verifySchemaTables(options: {
|
|
|
430
396
|
storageTypes,
|
|
431
397
|
normalizeDefault,
|
|
432
398
|
normalizeNativeType,
|
|
433
|
-
columnsCompatible,
|
|
434
399
|
} = options;
|
|
435
|
-
const contractDefaultControl = contract.defaultControl;
|
|
436
400
|
const issues: SchemaIssue[] = [];
|
|
437
401
|
const rootChildren: SchemaVerificationNode[] = [];
|
|
438
402
|
const schemaTables = schema.tables;
|
|
@@ -450,37 +414,27 @@ function verifySchemaTables(options: {
|
|
|
450
414
|
);
|
|
451
415
|
}
|
|
452
416
|
const contractTable = contractTableRaw;
|
|
453
|
-
const tableControlPolicy = effectiveControlPolicy(
|
|
454
|
-
contractTable.control,
|
|
455
|
-
contractDefaultControl,
|
|
456
|
-
);
|
|
457
417
|
const schemaTable = schemaTables[tableName];
|
|
458
418
|
const tablePath = `storage.namespaces.${namespaceId}.tables.${tableName}`;
|
|
459
419
|
|
|
460
420
|
if (!schemaTable) {
|
|
461
|
-
|
|
421
|
+
issues.push({
|
|
462
422
|
kind: 'missing_table',
|
|
463
423
|
table: tableName,
|
|
464
424
|
namespaceId,
|
|
465
425
|
message: `Table "${tableName}" is missing from database`,
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
{
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
actual: undefined,
|
|
479
|
-
children: [],
|
|
480
|
-
},
|
|
481
|
-
issues,
|
|
482
|
-
rootChildren,
|
|
483
|
-
);
|
|
426
|
+
});
|
|
427
|
+
rootChildren.push({
|
|
428
|
+
status: 'fail',
|
|
429
|
+
kind: 'table',
|
|
430
|
+
name: `table ${tableName}`,
|
|
431
|
+
contractPath: tablePath,
|
|
432
|
+
code: 'missing_table',
|
|
433
|
+
message: `Table "${tableName}" is missing`,
|
|
434
|
+
expected: undefined,
|
|
435
|
+
actual: undefined,
|
|
436
|
+
children: [],
|
|
437
|
+
});
|
|
484
438
|
continue;
|
|
485
439
|
}
|
|
486
440
|
|
|
@@ -490,7 +444,6 @@ function verifySchemaTables(options: {
|
|
|
490
444
|
tableName,
|
|
491
445
|
namespaceId,
|
|
492
446
|
tablePath,
|
|
493
|
-
tableControlPolicy,
|
|
494
447
|
issues,
|
|
495
448
|
strict,
|
|
496
449
|
typeMetadataRegistry,
|
|
@@ -498,7 +451,6 @@ function verifySchemaTables(options: {
|
|
|
498
451
|
storageTypes,
|
|
499
452
|
...ifDefined('normalizeDefault', normalizeDefault),
|
|
500
453
|
...ifDefined('normalizeNativeType', normalizeNativeType),
|
|
501
|
-
...ifDefined('columnsCompatible', columnsCompatible),
|
|
502
454
|
});
|
|
503
455
|
rootChildren.push(buildTableNode(tableName, tablePath, tableChildren));
|
|
504
456
|
}
|
|
@@ -510,29 +462,26 @@ function verifySchemaTables(options: {
|
|
|
510
462
|
(namespaceId) => contract.storage.namespaces[namespaceId]?.tables[tableName] !== undefined,
|
|
511
463
|
);
|
|
512
464
|
if (!claimed) {
|
|
513
|
-
|
|
514
|
-
|
|
465
|
+
// `namespaceId` is intentionally absent: an extra table exists in the
|
|
466
|
+
// live database but is not claimed by any contract namespace, so there
|
|
467
|
+
// is no contract coordinate to stamp here. Planners that consume this
|
|
468
|
+
// issue must handle the unstamped case (drop / quarantine by name).
|
|
469
|
+
issues.push({
|
|
515
470
|
kind: 'extra_table',
|
|
516
471
|
table: tableName,
|
|
517
472
|
message: `Extra table "${tableName}" found in database (not in contract)`,
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
{
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
actual: undefined,
|
|
531
|
-
children: [],
|
|
532
|
-
},
|
|
533
|
-
issues,
|
|
534
|
-
rootChildren,
|
|
535
|
-
);
|
|
473
|
+
});
|
|
474
|
+
rootChildren.push({
|
|
475
|
+
status: 'fail',
|
|
476
|
+
kind: 'table',
|
|
477
|
+
name: `table ${tableName}`,
|
|
478
|
+
contractPath: `storage.namespaces.*.tables.${tableName}`,
|
|
479
|
+
code: 'extra_table',
|
|
480
|
+
message: `Extra table "${tableName}" found`,
|
|
481
|
+
expected: undefined,
|
|
482
|
+
actual: undefined,
|
|
483
|
+
children: [],
|
|
484
|
+
});
|
|
536
485
|
}
|
|
537
486
|
}
|
|
538
487
|
}
|
|
@@ -546,7 +495,6 @@ function verifyTableChildren(options: {
|
|
|
546
495
|
tableName: string;
|
|
547
496
|
namespaceId: string;
|
|
548
497
|
tablePath: string;
|
|
549
|
-
tableControlPolicy: ControlPolicy;
|
|
550
498
|
issues: SchemaIssue[];
|
|
551
499
|
strict: boolean;
|
|
552
500
|
typeMetadataRegistry: ReadonlyMap<string, { nativeType?: string }>;
|
|
@@ -554,7 +502,6 @@ function verifyTableChildren(options: {
|
|
|
554
502
|
storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>;
|
|
555
503
|
normalizeDefault?: DefaultNormalizer;
|
|
556
504
|
normalizeNativeType?: NativeTypeNormalizer;
|
|
557
|
-
columnsCompatible?: ColumnsCompatible;
|
|
558
505
|
}): SchemaVerificationNode[] {
|
|
559
506
|
const {
|
|
560
507
|
contractTable,
|
|
@@ -562,7 +509,6 @@ function verifyTableChildren(options: {
|
|
|
562
509
|
tableName,
|
|
563
510
|
namespaceId,
|
|
564
511
|
tablePath,
|
|
565
|
-
tableControlPolicy,
|
|
566
512
|
issues,
|
|
567
513
|
strict,
|
|
568
514
|
typeMetadataRegistry,
|
|
@@ -570,7 +516,6 @@ function verifyTableChildren(options: {
|
|
|
570
516
|
storageTypes,
|
|
571
517
|
normalizeDefault,
|
|
572
518
|
normalizeNativeType,
|
|
573
|
-
columnsCompatible,
|
|
574
519
|
} = options;
|
|
575
520
|
const tableChildren: SchemaVerificationNode[] = [];
|
|
576
521
|
const columnNodes = collectContractColumnNodes({
|
|
@@ -579,7 +524,6 @@ function verifyTableChildren(options: {
|
|
|
579
524
|
tableName,
|
|
580
525
|
namespaceId,
|
|
581
526
|
tablePath,
|
|
582
|
-
tableControlPolicy,
|
|
583
527
|
issues,
|
|
584
528
|
strict,
|
|
585
529
|
typeMetadataRegistry,
|
|
@@ -587,7 +531,6 @@ function verifyTableChildren(options: {
|
|
|
587
531
|
storageTypes,
|
|
588
532
|
...ifDefined('normalizeDefault', normalizeDefault),
|
|
589
533
|
...ifDefined('normalizeNativeType', normalizeNativeType),
|
|
590
|
-
...ifDefined('columnsCompatible', columnsCompatible),
|
|
591
534
|
});
|
|
592
535
|
if (columnNodes.length > 0) {
|
|
593
536
|
tableChildren.push(buildColumnsNode(tablePath, columnNodes));
|
|
@@ -599,7 +542,6 @@ function verifyTableChildren(options: {
|
|
|
599
542
|
tableName,
|
|
600
543
|
namespaceId,
|
|
601
544
|
tablePath,
|
|
602
|
-
tableControlPolicy,
|
|
603
545
|
issues,
|
|
604
546
|
columnNodes,
|
|
605
547
|
});
|
|
@@ -611,7 +553,6 @@ function verifyTableChildren(options: {
|
|
|
611
553
|
schemaTable.primaryKey,
|
|
612
554
|
tableName,
|
|
613
555
|
namespaceId,
|
|
614
|
-
tableControlPolicy,
|
|
615
556
|
issues,
|
|
616
557
|
);
|
|
617
558
|
if (pkStatus === 'fail') {
|
|
@@ -626,18 +567,6 @@ function verifyTableChildren(options: {
|
|
|
626
567
|
actual: schemaTable.primaryKey,
|
|
627
568
|
children: [],
|
|
628
569
|
});
|
|
629
|
-
} else if (pkStatus === 'warn') {
|
|
630
|
-
tableChildren.push({
|
|
631
|
-
status: 'warn',
|
|
632
|
-
kind: 'primaryKey',
|
|
633
|
-
name: `primary key: ${contractTable.primaryKey.columns.join(', ')}`,
|
|
634
|
-
contractPath: `${tablePath}.primaryKey`,
|
|
635
|
-
code: 'primary_key_mismatch',
|
|
636
|
-
message: 'Primary key mismatch',
|
|
637
|
-
expected: contractTable.primaryKey,
|
|
638
|
-
actual: schemaTable.primaryKey,
|
|
639
|
-
children: [],
|
|
640
|
-
});
|
|
641
570
|
} else {
|
|
642
571
|
tableChildren.push({
|
|
643
572
|
status: 'pass',
|
|
@@ -652,29 +581,23 @@ function verifyTableChildren(options: {
|
|
|
652
581
|
});
|
|
653
582
|
}
|
|
654
583
|
} else if (schemaTable.primaryKey && strict) {
|
|
655
|
-
|
|
584
|
+
issues.push({
|
|
656
585
|
kind: 'extra_primary_key',
|
|
657
586
|
table: tableName,
|
|
658
587
|
namespaceId,
|
|
659
588
|
message: 'Extra primary key found in database (not in contract)',
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
{
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
actual: schemaTable.primaryKey,
|
|
673
|
-
children: [],
|
|
674
|
-
},
|
|
675
|
-
issues,
|
|
676
|
-
tableChildren,
|
|
677
|
-
);
|
|
589
|
+
});
|
|
590
|
+
tableChildren.push({
|
|
591
|
+
status: 'fail',
|
|
592
|
+
kind: 'primaryKey',
|
|
593
|
+
name: `primary key: ${schemaTable.primaryKey.columns.join(', ')}`,
|
|
594
|
+
contractPath: `${tablePath}.primaryKey`,
|
|
595
|
+
code: 'extra_primary_key',
|
|
596
|
+
message: 'Extra primary key found',
|
|
597
|
+
expected: undefined,
|
|
598
|
+
actual: schemaTable.primaryKey,
|
|
599
|
+
children: [],
|
|
600
|
+
});
|
|
678
601
|
}
|
|
679
602
|
|
|
680
603
|
// Verify FK constraints only for FKs with constraint: true.
|
|
@@ -688,7 +611,6 @@ function verifyTableChildren(options: {
|
|
|
688
611
|
tableName,
|
|
689
612
|
namespaceId,
|
|
690
613
|
tablePath,
|
|
691
|
-
tableControlPolicy,
|
|
692
614
|
issues,
|
|
693
615
|
strict,
|
|
694
616
|
);
|
|
@@ -702,7 +624,6 @@ function verifyTableChildren(options: {
|
|
|
702
624
|
tableName,
|
|
703
625
|
namespaceId,
|
|
704
626
|
tablePath,
|
|
705
|
-
tableControlPolicy,
|
|
706
627
|
issues,
|
|
707
628
|
strict,
|
|
708
629
|
);
|
|
@@ -727,7 +648,6 @@ function verifyTableChildren(options: {
|
|
|
727
648
|
tableName,
|
|
728
649
|
namespaceId,
|
|
729
650
|
tablePath,
|
|
730
|
-
tableControlPolicy,
|
|
731
651
|
issues,
|
|
732
652
|
strict,
|
|
733
653
|
);
|
|
@@ -742,7 +662,6 @@ function collectContractColumnNodes(options: {
|
|
|
742
662
|
tableName: string;
|
|
743
663
|
namespaceId: string;
|
|
744
664
|
tablePath: string;
|
|
745
|
-
tableControlPolicy: ControlPolicy;
|
|
746
665
|
issues: SchemaIssue[];
|
|
747
666
|
strict: boolean;
|
|
748
667
|
typeMetadataRegistry: ReadonlyMap<string, { nativeType?: string }>;
|
|
@@ -750,7 +669,6 @@ function collectContractColumnNodes(options: {
|
|
|
750
669
|
storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>;
|
|
751
670
|
normalizeDefault?: DefaultNormalizer;
|
|
752
671
|
normalizeNativeType?: NativeTypeNormalizer;
|
|
753
|
-
columnsCompatible?: ColumnsCompatible;
|
|
754
672
|
}): SchemaVerificationNode[] {
|
|
755
673
|
const {
|
|
756
674
|
contractTable,
|
|
@@ -758,7 +676,6 @@ function collectContractColumnNodes(options: {
|
|
|
758
676
|
tableName,
|
|
759
677
|
namespaceId,
|
|
760
678
|
tablePath,
|
|
761
|
-
tableControlPolicy,
|
|
762
679
|
issues,
|
|
763
680
|
strict,
|
|
764
681
|
typeMetadataRegistry,
|
|
@@ -766,7 +683,6 @@ function collectContractColumnNodes(options: {
|
|
|
766
683
|
storageTypes,
|
|
767
684
|
normalizeDefault,
|
|
768
685
|
normalizeNativeType,
|
|
769
|
-
columnsCompatible,
|
|
770
686
|
} = options;
|
|
771
687
|
const columnNodes: SchemaVerificationNode[] = [];
|
|
772
688
|
|
|
@@ -775,30 +691,24 @@ function collectContractColumnNodes(options: {
|
|
|
775
691
|
const columnPath = `${tablePath}.columns.${columnName}`;
|
|
776
692
|
|
|
777
693
|
if (!schemaColumn) {
|
|
778
|
-
|
|
694
|
+
issues.push({
|
|
779
695
|
kind: 'missing_column',
|
|
780
696
|
table: tableName,
|
|
781
697
|
namespaceId,
|
|
782
698
|
column: columnName,
|
|
783
699
|
message: `Column "${tableName}"."${columnName}" is missing from database`,
|
|
784
|
-
};
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
{
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
actual: undefined,
|
|
797
|
-
children: [],
|
|
798
|
-
},
|
|
799
|
-
issues,
|
|
800
|
-
columnNodes,
|
|
801
|
-
);
|
|
700
|
+
});
|
|
701
|
+
columnNodes.push({
|
|
702
|
+
status: 'fail',
|
|
703
|
+
kind: 'column',
|
|
704
|
+
name: `${columnName}: missing`,
|
|
705
|
+
contractPath: columnPath,
|
|
706
|
+
code: 'missing_column',
|
|
707
|
+
message: `Column "${columnName}" is missing`,
|
|
708
|
+
expected: undefined,
|
|
709
|
+
actual: undefined,
|
|
710
|
+
children: [],
|
|
711
|
+
});
|
|
802
712
|
continue;
|
|
803
713
|
}
|
|
804
714
|
|
|
@@ -810,7 +720,6 @@ function collectContractColumnNodes(options: {
|
|
|
810
720
|
contractColumn,
|
|
811
721
|
schemaColumn,
|
|
812
722
|
columnPath,
|
|
813
|
-
tableControlPolicy,
|
|
814
723
|
issues,
|
|
815
724
|
strict,
|
|
816
725
|
typeMetadataRegistry,
|
|
@@ -818,7 +727,6 @@ function collectContractColumnNodes(options: {
|
|
|
818
727
|
storageTypes,
|
|
819
728
|
...ifDefined('normalizeDefault', normalizeDefault),
|
|
820
729
|
...ifDefined('normalizeNativeType', normalizeNativeType),
|
|
821
|
-
...ifDefined('columnsCompatible', columnsCompatible),
|
|
822
730
|
}),
|
|
823
731
|
);
|
|
824
732
|
}
|
|
@@ -832,46 +740,31 @@ function appendExtraColumnNodes(options: {
|
|
|
832
740
|
tableName: string;
|
|
833
741
|
namespaceId: string;
|
|
834
742
|
tablePath: string;
|
|
835
|
-
tableControlPolicy: ControlPolicy;
|
|
836
743
|
issues: SchemaIssue[];
|
|
837
744
|
columnNodes: SchemaVerificationNode[];
|
|
838
745
|
}): void {
|
|
839
|
-
const {
|
|
840
|
-
|
|
841
|
-
schemaTable,
|
|
842
|
-
tableName,
|
|
843
|
-
namespaceId,
|
|
844
|
-
tablePath,
|
|
845
|
-
tableControlPolicy,
|
|
846
|
-
issues,
|
|
847
|
-
columnNodes,
|
|
848
|
-
} = options;
|
|
746
|
+
const { contractTable, schemaTable, tableName, namespaceId, tablePath, issues, columnNodes } =
|
|
747
|
+
options;
|
|
849
748
|
for (const [columnName, { nativeType }] of Object.entries(schemaTable.columns)) {
|
|
850
749
|
if (!contractTable.columns[columnName]) {
|
|
851
|
-
|
|
750
|
+
issues.push({
|
|
852
751
|
kind: 'extra_column',
|
|
853
752
|
table: tableName,
|
|
854
753
|
namespaceId,
|
|
855
754
|
column: columnName,
|
|
856
755
|
message: `Extra column "${tableName}"."${columnName}" found in database (not in contract)`,
|
|
857
|
-
};
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
{
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
actual: nativeType,
|
|
870
|
-
children: [],
|
|
871
|
-
},
|
|
872
|
-
issues,
|
|
873
|
-
columnNodes,
|
|
874
|
-
);
|
|
756
|
+
});
|
|
757
|
+
columnNodes.push({
|
|
758
|
+
status: 'fail',
|
|
759
|
+
kind: 'column',
|
|
760
|
+
name: `${columnName}: extra`,
|
|
761
|
+
contractPath: `${tablePath}.columns.${columnName}`,
|
|
762
|
+
code: 'extra_column',
|
|
763
|
+
message: `Extra column "${columnName}" found`,
|
|
764
|
+
expected: undefined,
|
|
765
|
+
actual: nativeType,
|
|
766
|
+
children: [],
|
|
767
|
+
});
|
|
875
768
|
}
|
|
876
769
|
}
|
|
877
770
|
}
|
|
@@ -883,7 +776,6 @@ function verifyColumn(options: {
|
|
|
883
776
|
contractColumn: StorageTable['columns'][string];
|
|
884
777
|
schemaColumn: SqlSchemaIR['tables'][string]['columns'][string];
|
|
885
778
|
columnPath: string;
|
|
886
|
-
tableControlPolicy: ControlPolicy;
|
|
887
779
|
issues: SchemaIssue[];
|
|
888
780
|
strict: boolean;
|
|
889
781
|
typeMetadataRegistry: ReadonlyMap<string, { nativeType?: string }>;
|
|
@@ -891,7 +783,6 @@ function verifyColumn(options: {
|
|
|
891
783
|
storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>;
|
|
892
784
|
normalizeDefault?: DefaultNormalizer;
|
|
893
785
|
normalizeNativeType?: NativeTypeNormalizer;
|
|
894
|
-
columnsCompatible?: ColumnsCompatible;
|
|
895
786
|
}): SchemaVerificationNode {
|
|
896
787
|
const {
|
|
897
788
|
tableName,
|
|
@@ -900,14 +791,12 @@ function verifyColumn(options: {
|
|
|
900
791
|
contractColumn,
|
|
901
792
|
schemaColumn,
|
|
902
793
|
columnPath,
|
|
903
|
-
tableControlPolicy,
|
|
904
794
|
issues,
|
|
905
795
|
strict,
|
|
906
796
|
codecHooks,
|
|
907
797
|
storageTypes,
|
|
908
798
|
normalizeDefault,
|
|
909
799
|
normalizeNativeType,
|
|
910
|
-
columnsCompatible,
|
|
911
800
|
} = options;
|
|
912
801
|
const columnChildren: SchemaVerificationNode[] = [];
|
|
913
802
|
let columnStatus: VerificationStatus = 'pass';
|
|
@@ -923,14 +812,8 @@ function verifyColumn(options: {
|
|
|
923
812
|
const schemaNativeType =
|
|
924
813
|
normalizeNativeType?.(schemaColumn.nativeType) ?? schemaColumn.nativeType;
|
|
925
814
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
(tableControlPolicy === 'external' &&
|
|
929
|
-
(columnsCompatible?.(contractNativeType, schemaNativeType) ??
|
|
930
|
-
contractNativeType === schemaNativeType));
|
|
931
|
-
|
|
932
|
-
if (!typesMatch) {
|
|
933
|
-
const issue: SchemaIssue = {
|
|
815
|
+
if (contractNativeType !== schemaNativeType) {
|
|
816
|
+
issues.push({
|
|
934
817
|
kind: 'type_mismatch',
|
|
935
818
|
table: tableName,
|
|
936
819
|
namespaceId,
|
|
@@ -938,23 +821,19 @@ function verifyColumn(options: {
|
|
|
938
821
|
expected: contractNativeType,
|
|
939
822
|
actual: schemaNativeType,
|
|
940
823
|
message: `Column "${tableName}"."${columnName}" has type mismatch: expected "${contractNativeType}", got "${schemaNativeType}"`,
|
|
941
|
-
};
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
children: [],
|
|
955
|
-
});
|
|
956
|
-
columnStatus = disposition;
|
|
957
|
-
}
|
|
824
|
+
});
|
|
825
|
+
columnChildren.push({
|
|
826
|
+
status: 'fail',
|
|
827
|
+
kind: 'type',
|
|
828
|
+
name: 'type',
|
|
829
|
+
contractPath: `${columnPath}.nativeType`,
|
|
830
|
+
code: 'type_mismatch',
|
|
831
|
+
message: `Type mismatch: expected ${contractNativeType}, got ${schemaNativeType}`,
|
|
832
|
+
expected: contractNativeType,
|
|
833
|
+
actual: schemaNativeType,
|
|
834
|
+
children: [],
|
|
835
|
+
});
|
|
836
|
+
columnStatus = 'fail';
|
|
958
837
|
}
|
|
959
838
|
|
|
960
839
|
if (resolvedContractColumn.codecId) {
|
|
@@ -990,7 +869,7 @@ function verifyColumn(options: {
|
|
|
990
869
|
}
|
|
991
870
|
|
|
992
871
|
if (contractColumn.nullable !== schemaColumn.nullable) {
|
|
993
|
-
|
|
872
|
+
issues.push({
|
|
994
873
|
kind: 'nullability_mismatch',
|
|
995
874
|
table: tableName,
|
|
996
875
|
namespaceId,
|
|
@@ -998,52 +877,44 @@ function verifyColumn(options: {
|
|
|
998
877
|
expected: String(contractColumn.nullable),
|
|
999
878
|
actual: String(schemaColumn.nullable),
|
|
1000
879
|
message: `Column "${tableName}"."${columnName}" has nullability mismatch: expected ${contractColumn.nullable ? 'nullable' : 'not null'}, got ${schemaColumn.nullable ? 'nullable' : 'not null'}`,
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
children: [],
|
|
1015
|
-
});
|
|
1016
|
-
columnStatus = disposition;
|
|
1017
|
-
}
|
|
880
|
+
});
|
|
881
|
+
columnChildren.push({
|
|
882
|
+
status: 'fail',
|
|
883
|
+
kind: 'nullability',
|
|
884
|
+
name: 'nullability',
|
|
885
|
+
contractPath: `${columnPath}.nullable`,
|
|
886
|
+
code: 'nullability_mismatch',
|
|
887
|
+
message: `Nullability mismatch: expected ${contractColumn.nullable ? 'nullable' : 'not null'}, got ${schemaColumn.nullable ? 'nullable' : 'not null'}`,
|
|
888
|
+
expected: contractColumn.nullable,
|
|
889
|
+
actual: schemaColumn.nullable,
|
|
890
|
+
children: [],
|
|
891
|
+
});
|
|
892
|
+
columnStatus = 'fail';
|
|
1018
893
|
}
|
|
1019
894
|
|
|
1020
895
|
if (contractColumn.default) {
|
|
1021
896
|
if (!schemaColumn.default) {
|
|
1022
897
|
const defaultDescription = describeColumnDefault(contractColumn.default);
|
|
1023
|
-
|
|
898
|
+
issues.push({
|
|
1024
899
|
kind: 'default_missing',
|
|
1025
900
|
table: tableName,
|
|
1026
901
|
namespaceId,
|
|
1027
902
|
column: columnName,
|
|
1028
903
|
expected: defaultDescription,
|
|
1029
904
|
message: `Column "${tableName}"."${columnName}" should have default ${defaultDescription} but database has no default`,
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
children: [],
|
|
1044
|
-
});
|
|
1045
|
-
columnStatus = disposition;
|
|
1046
|
-
}
|
|
905
|
+
});
|
|
906
|
+
columnChildren.push({
|
|
907
|
+
status: 'fail',
|
|
908
|
+
kind: 'default',
|
|
909
|
+
name: 'default',
|
|
910
|
+
contractPath: `${columnPath}.default`,
|
|
911
|
+
code: 'default_missing',
|
|
912
|
+
message: `Default missing: expected ${defaultDescription}`,
|
|
913
|
+
expected: defaultDescription,
|
|
914
|
+
actual: undefined,
|
|
915
|
+
children: [],
|
|
916
|
+
});
|
|
917
|
+
columnStatus = 'fail';
|
|
1047
918
|
} else if (
|
|
1048
919
|
!columnDefaultsEqual(
|
|
1049
920
|
contractColumn.default,
|
|
@@ -1053,8 +924,9 @@ function verifyColumn(options: {
|
|
|
1053
924
|
)
|
|
1054
925
|
) {
|
|
1055
926
|
const expectedDescription = describeColumnDefault(contractColumn.default);
|
|
927
|
+
// schemaColumn.default is now a raw string, describe it as-is
|
|
1056
928
|
const actualDescription = schemaColumn.default;
|
|
1057
|
-
|
|
929
|
+
issues.push({
|
|
1058
930
|
kind: 'default_mismatch',
|
|
1059
931
|
table: tableName,
|
|
1060
932
|
namespaceId,
|
|
@@ -1062,49 +934,41 @@ function verifyColumn(options: {
|
|
|
1062
934
|
expected: expectedDescription,
|
|
1063
935
|
actual: actualDescription,
|
|
1064
936
|
message: `Column "${tableName}"."${columnName}" has default mismatch: expected ${expectedDescription}, got ${actualDescription}`,
|
|
1065
|
-
};
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
children: [],
|
|
1079
|
-
});
|
|
1080
|
-
columnStatus = disposition;
|
|
1081
|
-
}
|
|
937
|
+
});
|
|
938
|
+
columnChildren.push({
|
|
939
|
+
status: 'fail',
|
|
940
|
+
kind: 'default',
|
|
941
|
+
name: 'default',
|
|
942
|
+
contractPath: `${columnPath}.default`,
|
|
943
|
+
code: 'default_mismatch',
|
|
944
|
+
message: `Default mismatch: expected ${expectedDescription}, got ${actualDescription}`,
|
|
945
|
+
expected: expectedDescription,
|
|
946
|
+
actual: actualDescription,
|
|
947
|
+
children: [],
|
|
948
|
+
});
|
|
949
|
+
columnStatus = 'fail';
|
|
1082
950
|
}
|
|
1083
951
|
} else if (strict && schemaColumn.default) {
|
|
1084
|
-
|
|
952
|
+
issues.push({
|
|
1085
953
|
kind: 'extra_default',
|
|
1086
954
|
table: tableName,
|
|
1087
955
|
namespaceId,
|
|
1088
956
|
column: columnName,
|
|
1089
957
|
actual: schemaColumn.default,
|
|
1090
958
|
message: `Column "${tableName}"."${columnName}" has default ${schemaColumn.default} in database but contract specifies no default`,
|
|
1091
|
-
};
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
children: [],
|
|
1105
|
-
});
|
|
1106
|
-
columnStatus = disposition;
|
|
1107
|
-
}
|
|
959
|
+
});
|
|
960
|
+
columnChildren.push({
|
|
961
|
+
status: 'fail',
|
|
962
|
+
kind: 'default',
|
|
963
|
+
name: 'default',
|
|
964
|
+
contractPath: `${columnPath}.default`,
|
|
965
|
+
code: 'extra_default',
|
|
966
|
+
message: `Extra default: ${schemaColumn.default}`,
|
|
967
|
+
expected: undefined,
|
|
968
|
+
actual: schemaColumn.default,
|
|
969
|
+
children: [],
|
|
970
|
+
});
|
|
971
|
+
columnStatus = 'fail';
|
|
1108
972
|
}
|
|
1109
973
|
|
|
1110
974
|
// Single-pass aggregation for better performance
|