@prisma-next/family-sql 0.14.0-dev.4 → 0.14.0-dev.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{authoring-type-constructors-CjFfO6LM.mjs → authoring-type-constructors-CXd-8ydc.mjs} +7 -30
- package/dist/authoring-type-constructors-CXd-8ydc.mjs.map +1 -0
- package/dist/contract-to-schema-ir-S-evq8E6.mjs +264 -0
- package/dist/contract-to-schema-ir-S-evq8E6.mjs.map +1 -0
- package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-DHYFuOBy.d.mts} +12 -7
- package/dist/{control-adapter-Cmw9LvEP.d.mts.map → control-adapter-DHYFuOBy.d.mts.map} +1 -1
- package/dist/control-adapter.d.mts +1 -1
- package/dist/control.d.mts +18 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +81 -943
- package/dist/control.mjs.map +1 -1
- package/dist/{schema-verify.d.mts → diff.d.mts} +5 -5
- package/dist/diff.d.mts.map +1 -0
- package/dist/{verify-sql-schema-xT4udQLQ.mjs → diff.mjs} +169 -253
- package/dist/diff.mjs.map +1 -0
- package/dist/ir.d.mts +9 -6
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/migration.d.mts +11 -2
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +7 -0
- package/dist/migration.mjs.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/psl-infer.d.mts +80 -0
- package/dist/psl-infer.d.mts.map +1 -0
- package/dist/psl-infer.mjs +334 -0
- package/dist/psl-infer.mjs.map +1 -0
- package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-CYtXg_zs.mjs} +17 -20
- package/dist/sql-contract-serializer-CYtXg_zs.mjs.map +1 -0
- package/dist/{verify-sql-schema-thU-jKpf.d.mts → sql-schema-diff-6z36dZt6.d.mts} +44 -5
- package/dist/sql-schema-diff-6z36dZt6.d.mts.map +1 -0
- package/dist/{types-kgstZ_Zd.d.mts → types-CkOIJXxU.d.mts} +84 -17
- package/dist/types-CkOIJXxU.d.mts.map +1 -0
- package/package.json +23 -23
- package/src/core/authoring-entity-types.ts +12 -37
- package/src/core/control-adapter.ts +10 -5
- package/src/core/control-instance.ts +174 -41
- package/src/core/{schema-verify/verify-sql-schema.ts → diff/sql-schema-diff.ts} +208 -16
- package/src/core/{schema-verify → diff}/verify-helpers.ts +12 -0
- package/src/core/ir/sql-contract-serializer-base.ts +23 -55
- package/src/core/ir/sql-contract-serializer.ts +5 -7
- package/src/core/migrations/contract-to-schema-ir.ts +49 -12
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/schema-differ.ts +40 -0
- package/src/core/migrations/types.ts +43 -2
- package/src/core/psl-contract-infer/printer-config.ts +0 -6
- package/src/core/sql-migration.ts +12 -1
- package/src/exports/control.ts +1 -0
- package/src/exports/diff.ts +18 -0
- package/src/exports/psl-infer.ts +36 -0
- package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
- package/dist/schema-verify.d.mts.map +0 -1
- package/dist/schema-verify.mjs +0 -2
- package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
- package/dist/test-utils.d.mts +0 -2
- package/dist/test-utils.mjs +0 -2
- package/dist/types-kgstZ_Zd.d.mts.map +0 -1
- package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
- package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
- package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
- package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
- package/src/exports/schema-verify.ts +0 -18
- package/src/exports/test-utils.ts +0 -9
- /package/src/core/{schema-verify → diff}/control-verify-emit.ts +0 -0
- /package/src/core/{schema-verify → diff}/verifier-disposition.ts +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Contract,
|
|
3
3
|
ContractMarkerRecord,
|
|
4
|
+
ControlPolicy,
|
|
4
5
|
LedgerEntryRecord,
|
|
5
6
|
} from '@prisma-next/contract/types';
|
|
7
|
+
import { effectiveControlPolicy } from '@prisma-next/contract/types';
|
|
6
8
|
import type {
|
|
7
9
|
TargetBoundComponentDescriptor,
|
|
8
10
|
TargetDescriptor,
|
|
@@ -15,6 +17,7 @@ import type {
|
|
|
15
17
|
OperationPreview,
|
|
16
18
|
OperationPreviewCapable,
|
|
17
19
|
PslContractInferCapable,
|
|
20
|
+
SchemaDiffIssue,
|
|
18
21
|
SchemaViewCapable,
|
|
19
22
|
SignDatabaseResult,
|
|
20
23
|
VerifyDatabaseResult,
|
|
@@ -22,12 +25,14 @@ import type {
|
|
|
22
25
|
} from '@prisma-next/framework-components/control';
|
|
23
26
|
import {
|
|
24
27
|
APP_SPACE_ID,
|
|
28
|
+
dispositionForCategory,
|
|
25
29
|
SchemaTreeNode,
|
|
26
30
|
VERIFY_CODE_HASH_MISMATCH,
|
|
27
31
|
VERIFY_CODE_MARKER_MISSING,
|
|
28
32
|
VERIFY_CODE_TARGET_MISMATCH,
|
|
29
33
|
} from '@prisma-next/framework-components/control';
|
|
30
34
|
import type { TypesImportSpec } from '@prisma-next/framework-components/emission';
|
|
35
|
+
import { isPlainRecord } from '@prisma-next/framework-components/ir';
|
|
31
36
|
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
32
37
|
import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces';
|
|
33
38
|
import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';
|
|
@@ -39,17 +44,17 @@ import type {
|
|
|
39
44
|
SqlExecuteRequest,
|
|
40
45
|
} from '@prisma-next/sql-relational-core/ast';
|
|
41
46
|
import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
|
|
42
|
-
import type {
|
|
47
|
+
import type { SqlSchemaIRNode, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
|
|
48
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
43
49
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
44
50
|
import type { SqlControlAdapter } from './control-adapter';
|
|
45
51
|
import { SqlContractSerializer } from './ir/sql-contract-serializer';
|
|
52
|
+
import type { DiffDatabaseSchemaInput } from './migrations/schema-differ';
|
|
46
53
|
import type {
|
|
47
54
|
SqlControlAdapterDescriptor,
|
|
48
55
|
SqlControlExtensionDescriptor,
|
|
49
56
|
} from './migrations/types';
|
|
50
57
|
import { sqlOperationsToPreview } from './operation-preview';
|
|
51
|
-
import { sqlSchemaIrToPslAst } from './psl-contract-infer/sql-schema-ir-to-psl-ast';
|
|
52
|
-
import { verifySqlSchema } from './schema-verify/verify-sql-schema';
|
|
53
58
|
import { collectSupportedCodecTypeIds } from './verify';
|
|
54
59
|
|
|
55
60
|
function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] {
|
|
@@ -186,9 +191,9 @@ interface SqlFamilyInstanceState {
|
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
export interface SqlControlFamilyInstance
|
|
189
|
-
extends ControlFamilyInstance<'sql',
|
|
190
|
-
SchemaViewCapable<
|
|
191
|
-
PslContractInferCapable<
|
|
194
|
+
extends ControlFamilyInstance<'sql', SqlSchemaIRNode>,
|
|
195
|
+
SchemaViewCapable<SqlSchemaIRNode>,
|
|
196
|
+
PslContractInferCapable<SqlSchemaIRNode>,
|
|
192
197
|
OperationPreviewCapable,
|
|
193
198
|
SqlFamilyInstanceState {
|
|
194
199
|
/**
|
|
@@ -210,18 +215,17 @@ export interface SqlControlFamilyInstance
|
|
|
210
215
|
}): Promise<VerifyDatabaseResult>;
|
|
211
216
|
|
|
212
217
|
/**
|
|
213
|
-
* Verify a contract against an already-introspected schema
|
|
218
|
+
* Verify a contract against an already-introspected schema.
|
|
214
219
|
*
|
|
215
220
|
* Callers that need to verify against the live database compose
|
|
216
221
|
* `introspect({ driver })` + `verifySchema({ contract, schema, ... })`.
|
|
217
|
-
* The aggregate verifier
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* as `extras`.
|
|
222
|
+
* The aggregate verifier hands in the full introspected schema and scopes
|
|
223
|
+
* the returned result to each member's contract space afterwards — so
|
|
224
|
+
* sibling-space tables never survive as `extras`.
|
|
221
225
|
*/
|
|
222
226
|
verifySchema(options: {
|
|
223
227
|
readonly contract: unknown;
|
|
224
|
-
readonly schema:
|
|
228
|
+
readonly schema: SqlSchemaIRNode;
|
|
225
229
|
readonly strict: boolean;
|
|
226
230
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
227
231
|
}): VerifyDatabaseSchemaResult;
|
|
@@ -236,9 +240,9 @@ export interface SqlControlFamilyInstance
|
|
|
236
240
|
introspect(options: {
|
|
237
241
|
readonly driver: SqlControlDriverInstance<string>;
|
|
238
242
|
readonly contract?: unknown;
|
|
239
|
-
}): Promise<
|
|
243
|
+
}): Promise<SqlSchemaIRNode>;
|
|
240
244
|
|
|
241
|
-
inferPslContract(schemaIR:
|
|
245
|
+
inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst;
|
|
242
246
|
|
|
243
247
|
lowerAst(
|
|
244
248
|
ast: AnyQueryAst | DdlNode,
|
|
@@ -294,8 +298,6 @@ export interface SqlControlFamilyInstance
|
|
|
294
298
|
|
|
295
299
|
bootstrapControlTableQueries(): readonly DdlNode[];
|
|
296
300
|
|
|
297
|
-
bootstrapSignMarkerQueries(): readonly DdlNode[];
|
|
298
|
-
|
|
299
301
|
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview;
|
|
300
302
|
}
|
|
301
303
|
|
|
@@ -523,12 +525,49 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
523
525
|
|
|
524
526
|
const targetSerializer = (
|
|
525
527
|
target as unknown as {
|
|
526
|
-
contractSerializer?: {
|
|
528
|
+
contractSerializer?: {
|
|
529
|
+
deserializeContract(json: unknown): Contract<SqlStorage>;
|
|
530
|
+
serializeContract(contract: Contract<SqlStorage>): unknown;
|
|
531
|
+
};
|
|
527
532
|
}
|
|
528
533
|
).contractSerializer;
|
|
529
|
-
|
|
534
|
+
// Database→PSL inference is target logic (it owns the dialect type/default
|
|
535
|
+
// maps and walks its own schema tree), so it is read off the descriptor like
|
|
536
|
+
// `contractSerializer`. Absent for targets without `contract infer` (Mongo).
|
|
537
|
+
const targetInferPslContract = blindCast<
|
|
538
|
+
{ readonly inferPslContract?: (schema: SqlSchemaIRNode) => PslDocumentAst },
|
|
539
|
+
'reading the optional target-descriptor inferPslContract hook'
|
|
540
|
+
>(target).inferPslContract;
|
|
541
|
+
// The combined database-schema verify is a required target-descriptor
|
|
542
|
+
// operation: every SQL target provides it, wrapping the same comparison
|
|
543
|
+
// `diffDatabaseSchema` runs in the verify envelope plus the pass/warn/fail
|
|
544
|
+
// tree the CLI renders. Read it off the descriptor like the other
|
|
545
|
+
// target-owned hooks.
|
|
546
|
+
const verifyDatabaseSchema = blindCast<
|
|
547
|
+
{
|
|
548
|
+
readonly verifyDatabaseSchema?: (
|
|
549
|
+
input: DiffDatabaseSchemaInput,
|
|
550
|
+
) => VerifyDatabaseSchemaResult;
|
|
551
|
+
},
|
|
552
|
+
'reading the required target-descriptor verifyDatabaseSchema hook'
|
|
553
|
+
>(target).verifyDatabaseSchema;
|
|
554
|
+
if (!verifyDatabaseSchema) {
|
|
555
|
+
throw new Error(
|
|
556
|
+
`SQL target "${target.targetId}" is missing the required verifyDatabaseSchema descriptor operation`,
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
const deserializeWithTargetSerializer = (contractOrJson: unknown): Contract<SqlStorage> => {
|
|
530
560
|
const serializer = targetSerializer ?? new SqlContractSerializer();
|
|
531
|
-
|
|
561
|
+
const json =
|
|
562
|
+
targetSerializer !== undefined && !isPlainRecord(contractOrJson)
|
|
563
|
+
? targetSerializer.serializeContract(
|
|
564
|
+
blindCast<
|
|
565
|
+
Contract<SqlStorage>,
|
|
566
|
+
'isPlainRecord returned false, so contractOrJson is a class instance, not raw JSON'
|
|
567
|
+
>(contractOrJson),
|
|
568
|
+
)
|
|
569
|
+
: contractOrJson;
|
|
570
|
+
return serializer.deserializeContract(json) as Contract<SqlStorage>;
|
|
532
571
|
};
|
|
533
572
|
|
|
534
573
|
return {
|
|
@@ -669,21 +708,47 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
669
708
|
|
|
670
709
|
verifySchema(options: {
|
|
671
710
|
readonly contract: unknown;
|
|
672
|
-
readonly schema:
|
|
711
|
+
readonly schema: SqlSchemaIRNode;
|
|
673
712
|
readonly strict: boolean;
|
|
674
713
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
675
714
|
}): VerifyDatabaseSchemaResult {
|
|
676
715
|
const contract = deserializeWithTargetSerializer(options.contract) as Contract<SqlStorage>;
|
|
677
|
-
|
|
678
|
-
|
|
716
|
+
// Verify is a thin consumer of the target's black-box comparison: it
|
|
717
|
+
// introspects the actual schema (already in `options.schema`), calls the
|
|
718
|
+
// target's required `verifyDatabaseSchema`, and rejects when a surviving
|
|
719
|
+
// issue is a failure. It composes no diffing itself and is blind to how
|
|
720
|
+
// the comparison works.
|
|
721
|
+
const sqlResult = verifyDatabaseSchema({
|
|
679
722
|
contract,
|
|
680
723
|
schema: options.schema,
|
|
681
724
|
strict: options.strict,
|
|
682
725
|
typeMetadataRegistry,
|
|
683
726
|
frameworkComponents: options.frameworkComponents,
|
|
684
|
-
...ifDefined('normalizeDefault', controlAdapter.normalizeDefault),
|
|
685
|
-
...ifDefined('normalizeNativeType', controlAdapter.normalizeNativeType),
|
|
686
727
|
});
|
|
728
|
+
// Control-policy suppression of the structural (e.g. RLS policy) diff
|
|
729
|
+
// issues is a verify-side post-step — the combined diff returns them
|
|
730
|
+
// ownership-filtered, and a suppressed control policy drops them here.
|
|
731
|
+
const schemaDiffIssues = filterSchemaDiffIssues(
|
|
732
|
+
sqlResult.schema.schemaDiffIssues,
|
|
733
|
+
contract.defaultControlPolicy,
|
|
734
|
+
);
|
|
735
|
+
const relationalFails = sqlResult.schema.counts.fail;
|
|
736
|
+
if (schemaDiffIssues.length === 0) {
|
|
737
|
+
if (schemaDiffIssues === sqlResult.schema.schemaDiffIssues) return sqlResult;
|
|
738
|
+
return { ...sqlResult, schema: { ...sqlResult.schema, schemaDiffIssues } };
|
|
739
|
+
}
|
|
740
|
+
const totalFails = relationalFails + schemaDiffIssues.length;
|
|
741
|
+
return {
|
|
742
|
+
...sqlResult,
|
|
743
|
+
ok: false,
|
|
744
|
+
code: sqlResult.code ?? 'PN-RUN-3010',
|
|
745
|
+
summary: `Database schema does not satisfy contract (${totalFails} failure${totalFails === 1 ? '' : 's'})`,
|
|
746
|
+
schema: {
|
|
747
|
+
...sqlResult.schema,
|
|
748
|
+
schemaDiffIssues,
|
|
749
|
+
counts: { ...sqlResult.schema.counts, fail: totalFails },
|
|
750
|
+
},
|
|
751
|
+
};
|
|
687
752
|
},
|
|
688
753
|
async sign(options: {
|
|
689
754
|
readonly driver: SqlControlDriverInstance<string>;
|
|
@@ -848,12 +913,17 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
848
913
|
async introspect(options: {
|
|
849
914
|
readonly driver: SqlControlDriverInstance<string>;
|
|
850
915
|
readonly contract?: unknown;
|
|
851
|
-
}): Promise<
|
|
916
|
+
}): Promise<SqlSchemaIRNode> {
|
|
852
917
|
return getControlAdapter().introspect(options.driver, options.contract);
|
|
853
918
|
},
|
|
854
919
|
|
|
855
|
-
inferPslContract(schemaIR:
|
|
856
|
-
|
|
920
|
+
inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst {
|
|
921
|
+
if (!targetInferPslContract) {
|
|
922
|
+
throw new Error(
|
|
923
|
+
`Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`,
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
return targetInferPslContract(schemaIR);
|
|
857
927
|
},
|
|
858
928
|
|
|
859
929
|
lowerAst(
|
|
@@ -867,17 +937,52 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
867
937
|
return getControlAdapter().bootstrapControlTableQueries();
|
|
868
938
|
},
|
|
869
939
|
|
|
870
|
-
bootstrapSignMarkerQueries(): readonly DdlNode[] {
|
|
871
|
-
return getControlAdapter().bootstrapSignMarkerQueries();
|
|
872
|
-
},
|
|
873
|
-
|
|
874
940
|
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {
|
|
875
941
|
return sqlOperationsToPreview(operations);
|
|
876
942
|
},
|
|
877
943
|
|
|
878
|
-
toSchemaView(schema:
|
|
879
|
-
|
|
880
|
-
|
|
944
|
+
toSchemaView(schema: SqlSchemaIRNode): CoreSchemaView {
|
|
945
|
+
// Walk the schema-IR tree's own structure (root → namespaces → tables)
|
|
946
|
+
// into one flat list of table nodes. A root that exposes a `namespaces`
|
|
947
|
+
// record (Postgres) contributes each namespace's tables; a flat root
|
|
948
|
+
// (SQLite) is its own single namespace. The single-schema common case
|
|
949
|
+
// renders the same table-level view as today — no synthetic namespace
|
|
950
|
+
// level.
|
|
951
|
+
const root = blindCast<
|
|
952
|
+
{
|
|
953
|
+
readonly namespaces?: Readonly<
|
|
954
|
+
Record<string, { readonly tables: Record<string, SqlTableIR> }>
|
|
955
|
+
>;
|
|
956
|
+
readonly tables?: Record<string, SqlTableIR>;
|
|
957
|
+
},
|
|
958
|
+
'structural read of the schema-IR tree own namespaces/tables records'
|
|
959
|
+
>(schema);
|
|
960
|
+
// A multi-namespace root qualifies every table's display name with its
|
|
961
|
+
// namespace key, so same-named tables in different namespaces (e.g.
|
|
962
|
+
// public.thing and auth.thing) render distinct ids and labels. The
|
|
963
|
+
// single-namespace case (and a flat SQLite root) keeps today's bare
|
|
964
|
+
// names. Synthesized constraint/index fallback names keep the bare table
|
|
965
|
+
// name — that is the name the database derives them from.
|
|
966
|
+
const namespaceEntries: ReadonlyArray<[string | undefined, Record<string, SqlTableIR>]> =
|
|
967
|
+
root.namespaces !== undefined
|
|
968
|
+
? Object.entries(root.namespaces).map(
|
|
969
|
+
([namespaceKey, namespace]): [string, Record<string, SqlTableIR>] => [
|
|
970
|
+
namespaceKey,
|
|
971
|
+
namespace.tables,
|
|
972
|
+
],
|
|
973
|
+
)
|
|
974
|
+
: [[undefined, root.tables ?? {}]];
|
|
975
|
+
const qualify = namespaceEntries.length > 1;
|
|
976
|
+
const tableEntries: ReadonlyArray<[string, string, SqlTableIR]> = namespaceEntries.flatMap(
|
|
977
|
+
([namespaceKey, tables]) =>
|
|
978
|
+
Object.entries(tables).map(([tableName, table]): [string, string, SqlTableIR] => [
|
|
979
|
+
qualify && namespaceKey !== undefined ? `${namespaceKey}.${tableName}` : tableName,
|
|
980
|
+
tableName,
|
|
981
|
+
table,
|
|
982
|
+
]),
|
|
983
|
+
);
|
|
984
|
+
const tableNodes: readonly SchemaTreeNode[] = tableEntries.map(
|
|
985
|
+
([displayName, tableName, table]: [string, string, SqlTableIR]) => {
|
|
881
986
|
const children: SchemaTreeNode[] = [];
|
|
882
987
|
|
|
883
988
|
const columnNodes: SchemaTreeNode[] = [];
|
|
@@ -888,7 +993,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
888
993
|
columnNodes.push(
|
|
889
994
|
new SchemaTreeNode({
|
|
890
995
|
kind: 'field',
|
|
891
|
-
id: `column-${
|
|
996
|
+
id: `column-${displayName}-${columnName}`,
|
|
892
997
|
label,
|
|
893
998
|
meta: {
|
|
894
999
|
nativeType: column.nativeType,
|
|
@@ -903,7 +1008,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
903
1008
|
children.push(
|
|
904
1009
|
new SchemaTreeNode({
|
|
905
1010
|
kind: 'collection',
|
|
906
|
-
id: `columns-${
|
|
1011
|
+
id: `columns-${displayName}`,
|
|
907
1012
|
label: 'columns',
|
|
908
1013
|
children: columnNodes,
|
|
909
1014
|
}),
|
|
@@ -915,7 +1020,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
915
1020
|
children.push(
|
|
916
1021
|
new SchemaTreeNode({
|
|
917
1022
|
kind: 'index',
|
|
918
|
-
id: `primary-key-${
|
|
1023
|
+
id: `primary-key-${displayName}`,
|
|
919
1024
|
label: `primary key: ${pkColumns}`,
|
|
920
1025
|
meta: {
|
|
921
1026
|
columns: table.primaryKey.columns,
|
|
@@ -931,7 +1036,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
931
1036
|
children.push(
|
|
932
1037
|
new SchemaTreeNode({
|
|
933
1038
|
kind: 'index',
|
|
934
|
-
id: `unique-${
|
|
1039
|
+
id: `unique-${displayName}-${name}`,
|
|
935
1040
|
label,
|
|
936
1041
|
meta: {
|
|
937
1042
|
columns: unique.columns,
|
|
@@ -947,7 +1052,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
947
1052
|
children.push(
|
|
948
1053
|
new SchemaTreeNode({
|
|
949
1054
|
kind: 'index',
|
|
950
|
-
id: `index-${
|
|
1055
|
+
id: `index-${displayName}-${name}`,
|
|
951
1056
|
label,
|
|
952
1057
|
meta: {
|
|
953
1058
|
columns: index.columns,
|
|
@@ -975,8 +1080,8 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
975
1080
|
|
|
976
1081
|
return new SchemaTreeNode({
|
|
977
1082
|
kind: 'entity',
|
|
978
|
-
id: `table-${
|
|
979
|
-
label: `table ${
|
|
1083
|
+
id: `table-${displayName}`,
|
|
1084
|
+
label: `table ${displayName}`,
|
|
980
1085
|
...(Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {}),
|
|
981
1086
|
...(children.length > 0 ? { children } : {}),
|
|
982
1087
|
});
|
|
@@ -994,3 +1099,31 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
994
1099
|
},
|
|
995
1100
|
};
|
|
996
1101
|
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Filters the structural schema-diff issues (from `diffDatabaseSchema`) through
|
|
1105
|
+
* the contract's `defaultControlPolicy`. Issues whose outcome maps to a suppressed
|
|
1106
|
+
* category under the effective policy are removed. This mirrors the control-policy
|
|
1107
|
+
* filtering applied by `verifySqlSchema` for table/column-level findings.
|
|
1108
|
+
*
|
|
1109
|
+
* Outcome → category mapping:
|
|
1110
|
+
* - `'extra'` → `'extraAuxiliary'` (an extra auxiliary entity, e.g. an RLS policy)
|
|
1111
|
+
* - `'missing'` → `'declaredMissing'`
|
|
1112
|
+
* - `'mismatch'` → `'declaredIncompatible'`
|
|
1113
|
+
*/
|
|
1114
|
+
export function filterSchemaDiffIssues(
|
|
1115
|
+
issues: readonly SchemaDiffIssue[],
|
|
1116
|
+
defaultControlPolicy: ControlPolicy | undefined,
|
|
1117
|
+
): readonly SchemaDiffIssue[] {
|
|
1118
|
+
if (issues.length === 0) return issues;
|
|
1119
|
+
const policy = effectiveControlPolicy(undefined, defaultControlPolicy);
|
|
1120
|
+
return issues.filter((issue) => {
|
|
1121
|
+
const category =
|
|
1122
|
+
issue.outcome === 'extra'
|
|
1123
|
+
? ('extraAuxiliary' as const)
|
|
1124
|
+
: issue.outcome === 'missing'
|
|
1125
|
+
? ('declaredMissing' as const)
|
|
1126
|
+
: ('declaredIncompatible' as const);
|
|
1127
|
+
return dispositionForCategory(policy, category) !== 'suppress';
|
|
1128
|
+
});
|
|
1129
|
+
}
|