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