@prisma-next/target-postgres 0.15.0-dev.2 → 0.15.0-dev.21
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-BtsOWgI3.mjs → authoring-34L18kA1.mjs} +18 -8
- package/dist/authoring-34L18kA1.mjs.map +1 -0
- package/dist/{codec-ids-CI144z7g.d.mts → codec-ids-CGmnXDxH.d.mts} +3 -2
- package/dist/codec-ids-CGmnXDxH.d.mts.map +1 -0
- package/dist/{codec-ids-B10ZZHzw.mjs → codec-ids-D3G8K0YM.mjs} +3 -2
- package/dist/codec-ids-D3G8K0YM.mjs.map +1 -0
- package/dist/codec-ids.d.mts +2 -2
- package/dist/codec-ids.mjs +2 -2
- package/dist/{codec-types-CTfT4U4O.d.mts → codec-types-CTlPXzZ9.d.mts} +2 -2
- package/dist/{codec-types-CTfT4U4O.d.mts.map → codec-types-CTlPXzZ9.d.mts.map} +1 -1
- package/dist/codec-types.d.mts +1 -1
- package/dist/{codecs-D7W9O0Xx.d.mts → codecs-B-76qSBx.d.mts} +35 -6
- package/dist/codecs-B-76qSBx.d.mts.map +1 -0
- package/dist/{codecs-DRbb26Or.mjs → codecs-TCsGgW-Y.mjs} +77 -5
- package/dist/codecs-TCsGgW-Y.mjs.map +1 -0
- package/dist/codecs.d.mts +2 -2
- package/dist/codecs.mjs +2 -2
- package/dist/contract-free.mjs +3 -3
- package/dist/{control-policy-1Tcf7Tyu.mjs → control-policy-42vVVpTL.mjs} +43 -74
- package/dist/control-policy-42vVVpTL.mjs.map +1 -0
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +64 -14
- package/dist/control.mjs.map +1 -1
- package/dist/{ddl-GTTOvjx_.mjs → ddl-DHXKzQ99.mjs} +3 -3
- package/dist/{ddl-GTTOvjx_.mjs.map → ddl-DHXKzQ99.mjs.map} +1 -1
- package/dist/default-normalizer-B9ZUiyUE.mjs +264 -0
- package/dist/default-normalizer-B9ZUiyUE.mjs.map +1 -0
- package/dist/default-normalizer.d.mts +14 -1
- package/dist/default-normalizer.d.mts.map +1 -1
- package/dist/default-normalizer.mjs +2 -248
- package/dist/{descriptor-meta-CFQKLXl_.mjs → descriptor-meta-qubYaxQA.mjs} +9 -4
- package/dist/descriptor-meta-qubYaxQA.mjs.map +1 -0
- package/dist/{diff-database-schema-Bfgshz4v.mjs → diff-database-schema-CrYO0j4_.mjs} +92 -26
- package/dist/diff-database-schema-CrYO0j4_.mjs.map +1 -0
- package/dist/diff-database-schema.d.mts +3 -12
- package/dist/diff-database-schema.d.mts.map +1 -1
- package/dist/diff-database-schema.mjs +1 -1
- package/dist/issue-planner.d.mts +1 -1
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +1 -1
- package/dist/migration.d.mts +2 -2
- package/dist/migration.mjs +2 -2
- package/dist/{op-factory-call-Bn9zDx4_.d.mts → op-factory-call-CvH0JeBW.d.mts} +7 -4
- package/dist/op-factory-call-CvH0JeBW.d.mts.map +1 -0
- package/dist/{op-factory-call-C38NzvQD.mjs → op-factory-call-rCP7TXTh.mjs} +35 -14
- package/dist/op-factory-call-rCP7TXTh.mjs.map +1 -0
- package/dist/op-factory-call.d.mts +2 -2
- package/dist/op-factory-call.mjs +2 -2
- package/dist/pack.d.mts +120 -3
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/{planner-BvUu6b9C.mjs → planner-Bj7MTEeR.mjs} +13 -13
- package/dist/planner-Bj7MTEeR.mjs.map +1 -0
- package/dist/{planner-produced-postgres-migration-D7y-PEHK.mjs → planner-produced-postgres-migration-iHdZVN5i.mjs} +2 -2
- package/dist/{planner-produced-postgres-migration-D7y-PEHK.mjs.map → planner-produced-postgres-migration-iHdZVN5i.mjs.map} +1 -1
- package/dist/{planner-produced-postgres-migration-BEY1T0Xm.d.mts → planner-produced-postgres-migration-k33bb9Sm.d.mts} +2 -2
- package/dist/{planner-produced-postgres-migration-BEY1T0Xm.d.mts.map → planner-produced-postgres-migration-k33bb9Sm.d.mts.map} +1 -1
- package/dist/planner-produced-postgres-migration.d.mts +1 -1
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/{planner-sql-checks-zqNfs2XZ.mjs → planner-sql-checks-BuB877wH.mjs} +2 -2
- package/dist/{planner-sql-checks-zqNfs2XZ.mjs.map → planner-sql-checks-BuB877wH.mjs.map} +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/planner.d.mts +2 -2
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +2 -2
- package/dist/{postgres-contract-view-DM8sN1Ot.mjs → postgres-contract-view-DebHMqGR.mjs} +4 -4
- package/dist/{postgres-contract-view-DM8sN1Ot.mjs.map → postgres-contract-view-DebHMqGR.mjs.map} +1 -1
- package/dist/{postgres-database-schema-node-CJv5bfbs.d.mts → postgres-database-schema-node-DJOTywwQ.d.mts} +11 -2
- package/dist/postgres-database-schema-node-DJOTywwQ.d.mts.map +1 -0
- package/dist/{postgres-migration-arMSWU9b.mjs → postgres-migration-fxgsblW1.mjs} +19 -3
- package/dist/postgres-migration-fxgsblW1.mjs.map +1 -0
- package/dist/{postgres-migration-DbckK1su.d.mts → postgres-migration-ulfqRGA1.d.mts} +27 -2
- package/dist/postgres-migration-ulfqRGA1.d.mts.map +1 -0
- package/dist/{postgres-role-schema-node-Cm-j0ZGM.mjs → postgres-role-schema-node-bg32e7I-.mjs} +3 -2
- package/dist/postgres-role-schema-node-bg32e7I-.mjs.map +1 -0
- package/dist/{postgres-schema-Cm4DtiGA.mjs → postgres-schema-CewLYHB7.mjs} +2 -2
- package/dist/{postgres-schema-Cm4DtiGA.mjs.map → postgres-schema-CewLYHB7.mjs.map} +1 -1
- package/dist/{resolve-ddl-schema-XyBaaenM.mjs → resolve-ddl-schema-D0wi_P9Q.mjs} +2 -2
- package/dist/{resolve-ddl-schema-XyBaaenM.mjs.map → resolve-ddl-schema-D0wi_P9Q.mjs.map} +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/schema-ir-annotations.mjs +1 -1
- package/dist/schema-node-kinds-CrYzCR1i.d.mts +30 -0
- package/dist/schema-node-kinds-CrYzCR1i.d.mts.map +1 -0
- package/dist/types.d.mts +3 -2
- package/dist/types.mjs +4 -4
- package/package.json +20 -20
- package/src/core/authoring.ts +18 -5
- package/src/core/codec-helpers.ts +53 -1
- package/src/core/codec-ids.ts +1 -0
- package/src/core/codecs.ts +56 -3
- package/src/core/default-normalizer.ts +22 -0
- package/src/core/descriptor-meta.ts +2 -0
- package/src/core/index-types.ts +15 -0
- package/src/core/migrations/contract-to-postgres-database-schema-node.ts +99 -19
- package/src/core/migrations/control-policy.ts +4 -3
- package/src/core/migrations/diff-database-schema.ts +6 -3
- package/src/core/migrations/issue-planner.ts +41 -80
- package/src/core/migrations/op-factory-call.ts +38 -14
- package/src/core/migrations/planner-strategies.ts +5 -4
- package/src/core/migrations/planner.ts +5 -4
- package/src/core/migrations/postgres-migration.ts +60 -0
- package/src/core/migrations/verify-postgres-namespaces.ts +0 -1
- package/src/core/psl-infer/infer-psl-contract.ts +130 -12
- package/src/core/schema-ir/postgres-policy-schema-node.ts +12 -2
- package/src/exports/codecs.ts +2 -0
- package/src/exports/control.ts +2 -0
- package/src/exports/default-normalizer.ts +1 -1
- package/src/exports/op-factory-call.ts +5 -0
- package/src/exports/types.ts +1 -0
- package/dist/authoring-BtsOWgI3.mjs.map +0 -1
- package/dist/codec-ids-B10ZZHzw.mjs.map +0 -1
- package/dist/codec-ids-CI144z7g.d.mts.map +0 -1
- package/dist/codecs-D7W9O0Xx.d.mts.map +0 -1
- package/dist/codecs-DRbb26Or.mjs.map +0 -1
- package/dist/control-policy-1Tcf7Tyu.mjs.map +0 -1
- package/dist/default-normalizer.mjs.map +0 -1
- package/dist/descriptor-meta-CFQKLXl_.mjs.map +0 -1
- package/dist/diff-database-schema-Bfgshz4v.mjs.map +0 -1
- package/dist/op-factory-call-Bn9zDx4_.d.mts.map +0 -1
- package/dist/op-factory-call-C38NzvQD.mjs.map +0 -1
- package/dist/planner-BvUu6b9C.mjs.map +0 -1
- package/dist/postgres-database-schema-node-CJv5bfbs.d.mts.map +0 -1
- package/dist/postgres-migration-DbckK1su.d.mts.map +0 -1
- package/dist/postgres-migration-arMSWU9b.mjs.map +0 -1
- package/dist/postgres-role-schema-node-Cm-j0ZGM.mjs.map +0 -1
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
} from '@prisma-next/family-sql/control';
|
|
26
26
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
27
27
|
import type { DiffableNode, SchemaDiffIssue } from '@prisma-next/framework-components/control';
|
|
28
|
+
import { issueOutcome, orderIssuesByDependencies } from '@prisma-next/framework-components/control';
|
|
28
29
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
29
30
|
import type { SqlStorage, StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
30
31
|
import type { DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
|
|
@@ -136,6 +137,7 @@ export interface IssuePlannerValue {
|
|
|
136
137
|
type CallCategory =
|
|
137
138
|
| 'dep'
|
|
138
139
|
| 'drop'
|
|
140
|
+
| 'dropType'
|
|
139
141
|
| 'table'
|
|
140
142
|
| 'rlsEnable'
|
|
141
143
|
| 'rlsPolicy'
|
|
@@ -151,6 +153,13 @@ type CallCategory =
|
|
|
151
153
|
* legacy walk-schema planner's emission order so `db init` and `db update`
|
|
152
154
|
* produce byte-identical plans for the shared shape (deps → drops → tables
|
|
153
155
|
* → columns → alters → PKs → uniques → indexes → FKs).
|
|
156
|
+
*
|
|
157
|
+
* `dropType` (DROP TYPE for a managed native enum) is its own bucket, ordered
|
|
158
|
+
* after the general drop bucket: a type can only be dropped once every table
|
|
159
|
+
* whose column uses it is gone. A column→enum edge would make this precise,
|
|
160
|
+
* but `coalesceSubtreeIssues` removes the column issue under a whole-table
|
|
161
|
+
* drop, so the edge never reaches the graph; the coarse bucket stands in until
|
|
162
|
+
* that edge lands (the coordinate work is a later slice).
|
|
154
163
|
*/
|
|
155
164
|
function classifyCall(call: PostgresOpFactoryCall): CallCategory {
|
|
156
165
|
switch (call.factoryName) {
|
|
@@ -159,8 +168,9 @@ function classifyCall(call: PostgresOpFactoryCall): CallCategory {
|
|
|
159
168
|
case 'createNativeEnumType':
|
|
160
169
|
case 'addNativeEnumValue':
|
|
161
170
|
return 'dep';
|
|
162
|
-
case 'dropTable':
|
|
163
171
|
case 'dropNativeEnumType':
|
|
172
|
+
return 'dropType';
|
|
173
|
+
case 'dropTable':
|
|
164
174
|
case 'dropColumn':
|
|
165
175
|
case 'dropConstraint':
|
|
166
176
|
case 'dropCheckConstraint':
|
|
@@ -369,57 +379,6 @@ export function columnTypeChanged(expected: SqlColumnIR, actual: SqlColumnIR): b
|
|
|
369
379
|
);
|
|
370
380
|
}
|
|
371
381
|
|
|
372
|
-
// ----------------------------------------------------------------------------
|
|
373
|
-
// Node-keyed issue ordering (re-keys ISSUE_KIND_ORDER on nodeKind + reason)
|
|
374
|
-
// ----------------------------------------------------------------------------
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Re-keys the legacy `ISSUE_KIND_ORDER` on `(nodeKind, reason)`, numbers
|
|
378
|
-
* preserved so the dependency intent stays legible. Final emission order is
|
|
379
|
-
* fixed downstream by `classifyCall` bucketing (dep → drop → table → column →
|
|
380
|
-
* recipe → alter → primaryKey → unique → index → foreignKey), so this only
|
|
381
|
-
* breaks ties within a bucket.
|
|
382
|
-
*/
|
|
383
|
-
export function nodeIssueOrder(issue: SchemaDiffIssue): number {
|
|
384
|
-
const node = issueNode(issue);
|
|
385
|
-
if (node === undefined) return 99;
|
|
386
|
-
switch (node.nodeKind) {
|
|
387
|
-
case PostgresSchemaNodeKind.namespace:
|
|
388
|
-
return 1;
|
|
389
|
-
case PostgresSchemaNodeKind.nativeEnum:
|
|
390
|
-
// Creates order right after namespace creates within the 'dep' bucket
|
|
391
|
-
// (CREATE SCHEMA before CREATE TYPE); drops order after table drops
|
|
392
|
-
// within the 'drop' bucket (DROP TYPE only after its dependents left).
|
|
393
|
-
return issue.reason === 'not-expected' ? 17 : 2;
|
|
394
|
-
case RelationalSchemaNodeKind.foreignKey:
|
|
395
|
-
return issue.reason === 'not-expected' ? 10 : 60;
|
|
396
|
-
case RelationalSchemaNodeKind.unique:
|
|
397
|
-
return issue.reason === 'not-expected' ? 11 : 51;
|
|
398
|
-
case RelationalSchemaNodeKind.primaryKey:
|
|
399
|
-
return issue.reason === 'not-expected' ? 12 : 50;
|
|
400
|
-
case RelationalSchemaNodeKind.index:
|
|
401
|
-
return issue.reason === 'not-expected' ? 13 : 52;
|
|
402
|
-
case RelationalSchemaNodeKind.columnDefault:
|
|
403
|
-
if (issue.reason === 'not-expected') return 14;
|
|
404
|
-
return issue.reason === 'not-found' ? 42 : 43;
|
|
405
|
-
case RelationalSchemaNodeKind.column:
|
|
406
|
-
if (issue.reason === 'not-expected') return 15;
|
|
407
|
-
return issue.reason === 'not-found' ? 30 : 40;
|
|
408
|
-
case PostgresSchemaNodeKind.table:
|
|
409
|
-
return issue.reason === 'not-expected' ? 16 : 20;
|
|
410
|
-
case RelationalSchemaNodeKind.check:
|
|
411
|
-
if (issue.reason === 'not-found') return 53;
|
|
412
|
-
return issue.reason === 'not-expected' ? 55 : 54;
|
|
413
|
-
default:
|
|
414
|
-
return 99;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/** Deterministic tiebreak within an order bucket: the diff path already encodes schema → table → child. */
|
|
419
|
-
export function nodeIssueKey(issue: SchemaDiffIssue): string {
|
|
420
|
-
return issue.path.join(' ');
|
|
421
|
-
}
|
|
422
|
-
|
|
423
382
|
// ----------------------------------------------------------------------------
|
|
424
383
|
// Subtree coalescing (the planner's responsibility per the differ's contract)
|
|
425
384
|
// ----------------------------------------------------------------------------
|
|
@@ -437,7 +396,7 @@ export function coalesceSubtreeIssues<TNode extends DiffableNode = DiffableNode>
|
|
|
437
396
|
issues: readonly SchemaDiffIssue<TNode>[],
|
|
438
397
|
): readonly SchemaDiffIssue<TNode>[] {
|
|
439
398
|
const collapsingPaths = issues
|
|
440
|
-
.filter((issue) => issue
|
|
399
|
+
.filter((issue) => issueOutcome(issue) !== 'not-equal')
|
|
441
400
|
.map((issue) => issue.path);
|
|
442
401
|
if (collapsingPaths.length === 0) return issues;
|
|
443
402
|
return issues.filter(
|
|
@@ -601,14 +560,14 @@ function mapNativeEnumNodeIssue(
|
|
|
601
560
|
);
|
|
602
561
|
}
|
|
603
562
|
const schemaName = emissionSchemaName(ctx, ddlSchemaName);
|
|
604
|
-
if (issue
|
|
563
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
605
564
|
const expected = blindCast<
|
|
606
565
|
PostgresNativeEnumSchemaNode,
|
|
607
566
|
'a not-found native-enum issue always carries the expected PostgresNativeEnumSchemaNode'
|
|
608
567
|
>(issue.expected);
|
|
609
568
|
return ok([new CreateNativeEnumTypeCall(schemaName, expected.typeName, expected.members)]);
|
|
610
569
|
}
|
|
611
|
-
if (issue
|
|
570
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
612
571
|
const actual = blindCast<
|
|
613
572
|
PostgresNativeEnumSchemaNode,
|
|
614
573
|
'a not-expected native-enum issue always carries the actual PostgresNativeEnumSchemaNode'
|
|
@@ -654,14 +613,14 @@ function mapTableNodeIssue(
|
|
|
654
613
|
ddlSchemaName: string,
|
|
655
614
|
codecHooks: ReadonlyMap<string, CodecControlHooks>,
|
|
656
615
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
657
|
-
if (issue
|
|
616
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
658
617
|
const table = blindCast<
|
|
659
618
|
PostgresTableSchemaNode,
|
|
660
619
|
'a not-found table issue always carries the expected PostgresTableSchemaNode'
|
|
661
620
|
>(issue.expected);
|
|
662
621
|
return ok(buildCreateTableCallsFromNode(schemaName, ddlSchemaName, table, codecHooks));
|
|
663
622
|
}
|
|
664
|
-
if (issue
|
|
623
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
665
624
|
const table = blindCast<
|
|
666
625
|
PostgresTableSchemaNode,
|
|
667
626
|
'a not-expected table issue always carries the actual PostgresTableSchemaNode'
|
|
@@ -703,7 +662,7 @@ function mapColumnNodeIssue(
|
|
|
703
662
|
tableName: string,
|
|
704
663
|
codecHooks: ReadonlyMap<string, CodecControlHooks>,
|
|
705
664
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
706
|
-
if (issue
|
|
665
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
707
666
|
const column = blindCast<
|
|
708
667
|
SqlColumnIR,
|
|
709
668
|
'a not-found column issue always carries the expected column node'
|
|
@@ -712,7 +671,7 @@ function mapColumnNodeIssue(
|
|
|
712
671
|
new AddColumnCall(schemaName, tableName, renderColumnDdl(column.name, column, codecHooks)),
|
|
713
672
|
]);
|
|
714
673
|
}
|
|
715
|
-
if (issue
|
|
674
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
716
675
|
const column = blindCast<
|
|
717
676
|
SqlColumnIR,
|
|
718
677
|
'a not-expected column issue always carries the actual column node'
|
|
@@ -755,7 +714,7 @@ function mapColumnDefaultNodeIssue(
|
|
|
755
714
|
tableName: string,
|
|
756
715
|
columnName: string,
|
|
757
716
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
758
|
-
if (issue
|
|
717
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
759
718
|
return ok([new DropDefaultCall(schemaName, tableName, columnName)]);
|
|
760
719
|
}
|
|
761
720
|
// not-found (SET DEFAULT, additive) or not-equal (SET DEFAULT, widening).
|
|
@@ -772,7 +731,7 @@ function mapColumnDefaultNodeIssue(
|
|
|
772
731
|
tableName,
|
|
773
732
|
columnName,
|
|
774
733
|
defaultSql,
|
|
775
|
-
issue
|
|
734
|
+
issueOutcome(issue) === 'not-equal' ? 'widening' : 'additive',
|
|
776
735
|
),
|
|
777
736
|
]);
|
|
778
737
|
}
|
|
@@ -782,7 +741,7 @@ function mapPrimaryKeyNodeIssue(
|
|
|
782
741
|
schemaName: string,
|
|
783
742
|
tableName: string,
|
|
784
743
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
785
|
-
if (issue
|
|
744
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
786
745
|
const pk = blindCast<
|
|
787
746
|
{ readonly columns: readonly string[]; readonly name?: string },
|
|
788
747
|
'a not-found primary-key issue always carries the expected PrimaryKey node'
|
|
@@ -790,7 +749,7 @@ function mapPrimaryKeyNodeIssue(
|
|
|
790
749
|
const constraintName = pk.name ?? `${tableName}_pkey`;
|
|
791
750
|
return ok([new AddPrimaryKeyCall(schemaName, tableName, constraintName, [...pk.columns])]);
|
|
792
751
|
}
|
|
793
|
-
if (issue
|
|
752
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
794
753
|
const pk = blindCast<
|
|
795
754
|
{ readonly name?: string },
|
|
796
755
|
'a not-expected primary-key issue always carries the actual PrimaryKey node'
|
|
@@ -807,14 +766,14 @@ function mapForeignKeyNodeIssue(
|
|
|
807
766
|
schemaName: string,
|
|
808
767
|
tableName: string,
|
|
809
768
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
810
|
-
if (issue
|
|
769
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
811
770
|
const fk = blindCast<
|
|
812
771
|
SqlForeignKeyIR,
|
|
813
772
|
'a not-found foreign-key issue always carries the expected foreign-key node'
|
|
814
773
|
>(issue.expected);
|
|
815
774
|
return ok([new AddForeignKeyCall(schemaName, tableName, fkSpecFromNode(fk, tableName))]);
|
|
816
775
|
}
|
|
817
|
-
if (issue
|
|
776
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
818
777
|
const fk = blindCast<
|
|
819
778
|
SqlForeignKeyIR,
|
|
820
779
|
'a not-expected foreign-key issue always carries the actual foreign-key node'
|
|
@@ -830,7 +789,7 @@ function mapUniqueNodeIssue(
|
|
|
830
789
|
schemaName: string,
|
|
831
790
|
tableName: string,
|
|
832
791
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
833
|
-
if (issue
|
|
792
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
834
793
|
const unique = blindCast<
|
|
835
794
|
SqlUniqueIR,
|
|
836
795
|
'a not-found unique issue always carries the expected unique node'
|
|
@@ -838,7 +797,7 @@ function mapUniqueNodeIssue(
|
|
|
838
797
|
const name = unique.name ?? `${tableName}_${unique.columns.join('_')}_key`;
|
|
839
798
|
return ok([new AddUniqueCall(schemaName, tableName, name, [...unique.columns])]);
|
|
840
799
|
}
|
|
841
|
-
if (issue
|
|
800
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
842
801
|
const unique = blindCast<
|
|
843
802
|
SqlUniqueIR,
|
|
844
803
|
'a not-expected unique issue always carries the actual unique node'
|
|
@@ -854,7 +813,7 @@ function mapIndexNodeIssue(
|
|
|
854
813
|
schemaName: string,
|
|
855
814
|
tableName: string,
|
|
856
815
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
857
|
-
if (issue
|
|
816
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
858
817
|
const index = blindCast<
|
|
859
818
|
SqlIndexIR,
|
|
860
819
|
'a not-found index issue always carries the expected index node'
|
|
@@ -865,7 +824,7 @@ function mapIndexNodeIssue(
|
|
|
865
824
|
if (index.options !== undefined) extras.options = index.options;
|
|
866
825
|
return ok([new CreateIndexCall(schemaName, tableName, indexName, [...index.columns], extras)]);
|
|
867
826
|
}
|
|
868
|
-
if (issue
|
|
827
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
869
828
|
const index = blindCast<
|
|
870
829
|
SqlIndexIR,
|
|
871
830
|
'a not-expected index issue always carries the actual index node'
|
|
@@ -885,7 +844,7 @@ function mapCheckNodeIssue(
|
|
|
885
844
|
// the default mapper handles directly; check_missing / check_mismatch are
|
|
886
845
|
// consumed by `checkConstraintPlanCallStrategy` (drop+recreate), so reaching
|
|
887
846
|
// here for them means the strategy did not run — a conflict.
|
|
888
|
-
if (issue
|
|
847
|
+
if (issueOutcome(issue) === 'not-expected') {
|
|
889
848
|
const check = blindCast<
|
|
890
849
|
{ readonly name: string },
|
|
891
850
|
'a not-expected check issue always carries the actual check node'
|
|
@@ -902,8 +861,8 @@ function mapCheckNodeIssue(
|
|
|
902
861
|
|
|
903
862
|
/**
|
|
904
863
|
* Maps one node-typed diff issue to its migration call(s), dispatching on the
|
|
905
|
-
* node's `nodeKind`
|
|
906
|
-
* from `codecRef` via `column-ddl-rendering.ts`.
|
|
864
|
+
* node's `nodeKind` and the issue's presence-derived `issueOutcome`, reading
|
|
865
|
+
* nodes and resolving column DDL from `codecRef` via `column-ddl-rendering.ts`.
|
|
907
866
|
*/
|
|
908
867
|
export function mapNodeIssueToCall(
|
|
909
868
|
issue: SchemaDiffIssue,
|
|
@@ -919,7 +878,7 @@ export function mapNodeIssueToCall(
|
|
|
919
878
|
);
|
|
920
879
|
}
|
|
921
880
|
if (node.nodeKind === PostgresSchemaNodeKind.namespace) {
|
|
922
|
-
if (issue
|
|
881
|
+
if (issueOutcome(issue) !== 'not-found') {
|
|
923
882
|
return notOk(
|
|
924
883
|
nodeConflict('unsupportedOperation', `Unexpected namespace drift: ${issue.path.join('/')}`),
|
|
925
884
|
);
|
|
@@ -1052,13 +1011,12 @@ export function planIssues(
|
|
|
1052
1011
|
}
|
|
1053
1012
|
}
|
|
1054
1013
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
});
|
|
1014
|
+
// Dependency-graph order: a topological sort over the issues' `dependsOn`
|
|
1015
|
+
// cross-links and containment edges (the ordering law drives edge direction
|
|
1016
|
+
// by presence), path-tiebroken for determinism. `classifyCall` bucketing
|
|
1017
|
+
// downstream fixes the coarse cross-entity DDL sequence; this settles the
|
|
1018
|
+
// order within each bucket (notably the teardown order among drops).
|
|
1019
|
+
const sorted = orderIssuesByDependencies(remaining);
|
|
1062
1020
|
|
|
1063
1021
|
const defaultCalls: PostgresOpFactoryCall[] = [];
|
|
1064
1022
|
const conflicts: SqlPlannerConflict[] = [];
|
|
@@ -1106,6 +1064,9 @@ export function planIssues(
|
|
|
1106
1064
|
const calls: PostgresOpFactoryCall[] = [
|
|
1107
1065
|
...byCategory('dep'),
|
|
1108
1066
|
...byCategory('drop'),
|
|
1067
|
+
// DROP TYPE after every table drop — a managed enum type can only go once
|
|
1068
|
+
// no live column still uses it.
|
|
1069
|
+
...byCategory('dropType'),
|
|
1109
1070
|
...byCategory('table'),
|
|
1110
1071
|
...byCategory('column'),
|
|
1111
1072
|
...gatedRecipe,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
tableExistsAst,
|
|
46
46
|
} from '../../contract-free/checks';
|
|
47
47
|
import * as contractFreeDdl from '../../contract-free/ddl';
|
|
48
|
-
import type { PostgresRlsPolicy } from '../postgres-rls-policy';
|
|
48
|
+
import type { PostgresRlsPolicy, PostgresRlsPolicyInput } from '../postgres-rls-policy';
|
|
49
49
|
import {
|
|
50
50
|
escapeLiteral,
|
|
51
51
|
quoteIdentifier,
|
|
@@ -740,10 +740,6 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
|
|
|
740
740
|
renderTypeScript(): string {
|
|
741
741
|
return `rawSql(${jsonToTsSource({ id: `column.${this.tableName}.${this.columnName}`, label: this.label, operationClass: 'additive' })})`;
|
|
742
742
|
}
|
|
743
|
-
|
|
744
|
-
override importRequirements(): readonly ImportRequirement[] {
|
|
745
|
-
return [];
|
|
746
|
-
}
|
|
747
743
|
}
|
|
748
744
|
|
|
749
745
|
/**
|
|
@@ -810,10 +806,6 @@ export class AddNotNullColumnWithTempDefaultCall extends PostgresOpFactoryCallNo
|
|
|
810
806
|
renderTypeScript(): string {
|
|
811
807
|
return `rawSql(${jsonToTsSource({ id: `column.${this.tableName}.${this.columnName}`, label: this.label, operationClass: 'additive' })})`;
|
|
812
808
|
}
|
|
813
|
-
|
|
814
|
-
override importRequirements(): readonly ImportRequirement[] {
|
|
815
|
-
return [];
|
|
816
|
-
}
|
|
817
809
|
}
|
|
818
810
|
|
|
819
811
|
// ============================================================================
|
|
@@ -1592,7 +1584,23 @@ export class CreatePostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
|
|
|
1592
1584
|
}
|
|
1593
1585
|
|
|
1594
1586
|
renderTypeScript(): string {
|
|
1595
|
-
|
|
1587
|
+
const p = this.policy;
|
|
1588
|
+
const input: PostgresRlsPolicyInput = {
|
|
1589
|
+
name: p.name,
|
|
1590
|
+
prefix: p.prefix,
|
|
1591
|
+
tableName: p.tableName,
|
|
1592
|
+
namespaceId: p.namespaceId,
|
|
1593
|
+
operation: p.operation,
|
|
1594
|
+
roles: p.roles,
|
|
1595
|
+
...ifDefined('using', p.using),
|
|
1596
|
+
...ifDefined('withCheck', p.withCheck),
|
|
1597
|
+
permissive: p.permissive,
|
|
1598
|
+
};
|
|
1599
|
+
return `this.createRlsPolicy({ schema: ${jsonToTsSource(this.schemaName)}, table: ${jsonToTsSource(this.tableName)}, policy: ${jsonToTsSource(input)} })`;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
1603
|
+
return [];
|
|
1596
1604
|
}
|
|
1597
1605
|
}
|
|
1598
1606
|
|
|
@@ -1623,7 +1631,11 @@ export class DropPostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
|
|
|
1623
1631
|
}
|
|
1624
1632
|
|
|
1625
1633
|
renderTypeScript(): string {
|
|
1626
|
-
return `dropRlsPolicy(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.policyName)})`;
|
|
1634
|
+
return `this.dropRlsPolicy({ schema: ${jsonToTsSource(this.schemaName)}, table: ${jsonToTsSource(this.tableName)}, policy: ${jsonToTsSource(this.policyName)} })`;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
1638
|
+
return [];
|
|
1627
1639
|
}
|
|
1628
1640
|
}
|
|
1629
1641
|
|
|
@@ -1652,7 +1664,11 @@ export class EnableRowLevelSecurityCall extends PostgresOpFactoryCallNode {
|
|
|
1652
1664
|
}
|
|
1653
1665
|
|
|
1654
1666
|
renderTypeScript(): string {
|
|
1655
|
-
return `enableRowLevelSecurity(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)})`;
|
|
1667
|
+
return `this.enableRowLevelSecurity({ schema: ${jsonToTsSource(this.schemaName)}, table: ${jsonToTsSource(this.tableName)} })`;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
1671
|
+
return [];
|
|
1656
1672
|
}
|
|
1657
1673
|
}
|
|
1658
1674
|
|
|
@@ -1681,7 +1697,11 @@ export class DisableRowLevelSecurityCall extends PostgresOpFactoryCallNode {
|
|
|
1681
1697
|
}
|
|
1682
1698
|
|
|
1683
1699
|
renderTypeScript(): string {
|
|
1684
|
-
return `disableRowLevelSecurity(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)})`;
|
|
1700
|
+
return `this.disableRowLevelSecurity({ schema: ${jsonToTsSource(this.schemaName)}, table: ${jsonToTsSource(this.tableName)} })`;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
1704
|
+
return [];
|
|
1685
1705
|
}
|
|
1686
1706
|
}
|
|
1687
1707
|
|
|
@@ -1724,7 +1744,11 @@ export class RenamePostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
|
|
|
1724
1744
|
}
|
|
1725
1745
|
|
|
1726
1746
|
renderTypeScript(): string {
|
|
1727
|
-
return `renameRlsPolicy(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.oldPolicyName)}, ${jsonToTsSource(this.newPolicyName)})`;
|
|
1747
|
+
return `this.renameRlsPolicy({ schema: ${jsonToTsSource(this.schemaName)}, table: ${jsonToTsSource(this.tableName)}, from: ${jsonToTsSource(this.oldPolicyName)}, to: ${jsonToTsSource(this.newPolicyName)} })`;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
override importRequirements(): readonly ImportRequirement[] {
|
|
1751
|
+
return [];
|
|
1728
1752
|
}
|
|
1729
1753
|
}
|
|
1730
1754
|
|
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
import { resolveValueSetValues } from '@prisma-next/family-sql/control';
|
|
36
36
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
37
37
|
import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
|
|
38
|
+
import { issueOutcome } from '@prisma-next/framework-components/control';
|
|
38
39
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
39
40
|
import {
|
|
40
41
|
type SqlStorage,
|
|
@@ -198,7 +199,7 @@ export const notNullBackfillCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
198
199
|
const calls: PostgresOpFactoryCall[] = [];
|
|
199
200
|
|
|
200
201
|
for (const issue of issues) {
|
|
201
|
-
if (issue
|
|
202
|
+
if (issueOutcome(issue) !== 'not-found') continue;
|
|
202
203
|
const node = issueNode(issue);
|
|
203
204
|
if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.column) continue;
|
|
204
205
|
const expected = blindCast<
|
|
@@ -263,7 +264,7 @@ export const typeChangeCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
|
263
264
|
const calls: PostgresOpFactoryCall[] = [];
|
|
264
265
|
|
|
265
266
|
for (const issue of issues) {
|
|
266
|
-
if (issue
|
|
267
|
+
if (issueOutcome(issue) !== 'not-equal') continue;
|
|
267
268
|
const pair = columnNodePair(issue);
|
|
268
269
|
if (pair === undefined) continue;
|
|
269
270
|
const { expected, actual } = pair;
|
|
@@ -357,7 +358,7 @@ export const nullableTighteningCallStrategy: CallMigrationStrategy = (issues, ct
|
|
|
357
358
|
const calls: PostgresOpFactoryCall[] = [];
|
|
358
359
|
|
|
359
360
|
for (const issue of issues) {
|
|
360
|
-
if (issue
|
|
361
|
+
if (issueOutcome(issue) !== 'not-equal') continue;
|
|
361
362
|
const pair = columnNodePair(issue);
|
|
362
363
|
if (pair === undefined) continue;
|
|
363
364
|
const { expected, actual } = pair;
|
|
@@ -590,7 +591,7 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
590
591
|
const mutableStorageTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
|
|
591
592
|
|
|
592
593
|
for (const issue of issues) {
|
|
593
|
-
if (issue
|
|
594
|
+
if (issueOutcome(issue) !== 'not-found') continue;
|
|
594
595
|
const node = issueNode(issue);
|
|
595
596
|
if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.column) continue;
|
|
596
597
|
const expected = blindCast<
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
SchemaDiffIssue,
|
|
22
22
|
SchemaOwnership,
|
|
23
23
|
} from '@prisma-next/framework-components/control';
|
|
24
|
+
import { issueOutcome } from '@prisma-next/framework-components/control';
|
|
24
25
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
25
26
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
26
27
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
@@ -211,7 +212,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
211
212
|
options.policy.allowedOperationClasses.includes('destructive');
|
|
212
213
|
const coalesced = coalesceSubtreeIssues(relationalDiffIssues);
|
|
213
214
|
const owned = retainUnownedExtras(coalesced, options.ownership, options.contract);
|
|
214
|
-
const gated = strict ? owned : owned.filter((issue) => issue
|
|
215
|
+
const gated = strict ? owned : owned.filter((issue) => issueOutcome(issue) !== 'not-expected');
|
|
215
216
|
|
|
216
217
|
// Namespace presence (`CREATE SCHEMA`) is a planner-only op-generation
|
|
217
218
|
// concern stitched in here rather than inside the shared diff — verify
|
|
@@ -369,7 +370,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
369
370
|
// 'not-equal' is unreachable for content-addressed policies: the wire name
|
|
370
371
|
// encodes the body hash, so two policies sharing a local key (same name)
|
|
371
372
|
// are always equal and isEqualTo never returns false.
|
|
372
|
-
if (issue
|
|
373
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
373
374
|
const expected = issue.expected;
|
|
374
375
|
PostgresPolicySchemaNode.assert(expected);
|
|
375
376
|
// expected.namespaceId is the DDL schema name (resolved during projection);
|
|
@@ -381,7 +382,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
381
382
|
expected.namespaceId,
|
|
382
383
|
),
|
|
383
384
|
});
|
|
384
|
-
} else if (issue
|
|
385
|
+
} else if (issueOutcome(issue) === 'not-expected') {
|
|
385
386
|
const actual = issue.actual;
|
|
386
387
|
PostgresPolicySchemaNode.assert(actual);
|
|
387
388
|
extra.push({
|
|
@@ -505,7 +506,7 @@ function retainUnownedExtras(
|
|
|
505
506
|
): readonly SchemaDiffIssue<SqlSchemaDiffNode>[] {
|
|
506
507
|
if (ownership === undefined) return issues;
|
|
507
508
|
return issues.filter((issue) => {
|
|
508
|
-
if (issue
|
|
509
|
+
if (issueOutcome(issue) !== 'not-expected') return true;
|
|
509
510
|
const node = issueNode(issue);
|
|
510
511
|
if (node === undefined) return true;
|
|
511
512
|
const coordinate = postgresNodeStorageCoordinate(node);
|
|
@@ -8,6 +8,7 @@ import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
|
8
8
|
import type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
|
|
9
9
|
import { errorPostgresMigrationStackMissing } from '../errors';
|
|
10
10
|
import { PostgresContractView } from '../postgres-contract-view';
|
|
11
|
+
import { PostgresRlsPolicy, type PostgresRlsPolicyInput } from '../postgres-rls-policy';
|
|
11
12
|
import {
|
|
12
13
|
AddCheckConstraintCall,
|
|
13
14
|
AddColumnCall,
|
|
@@ -19,8 +20,10 @@ import {
|
|
|
19
20
|
type AlterColumnTypeOptions,
|
|
20
21
|
CreateIndexCall,
|
|
21
22
|
CreateNativeEnumTypeCall,
|
|
23
|
+
CreatePostgresRlsPolicyCall,
|
|
22
24
|
CreateSchemaCall,
|
|
23
25
|
CreateTableCall,
|
|
26
|
+
DisableRowLevelSecurityCall,
|
|
24
27
|
DropCheckConstraintCall,
|
|
25
28
|
DropColumnCall,
|
|
26
29
|
DropConstraintCall,
|
|
@@ -28,7 +31,10 @@ import {
|
|
|
28
31
|
DropIndexCall,
|
|
29
32
|
DropNativeEnumTypeCall,
|
|
30
33
|
DropNotNullCall,
|
|
34
|
+
DropPostgresRlsPolicyCall,
|
|
31
35
|
DropTableCall,
|
|
36
|
+
EnableRowLevelSecurityCall,
|
|
37
|
+
RenamePostgresRlsPolicyCall,
|
|
32
38
|
SetDefaultCall,
|
|
33
39
|
SetNotNullCall,
|
|
34
40
|
} from './op-factory-call';
|
|
@@ -420,4 +426,58 @@ export abstract class PostgresMigration<
|
|
|
420
426
|
}): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
|
|
421
427
|
return installExtension(options, this.controlAdapterFor('installExtension'));
|
|
422
428
|
}
|
|
429
|
+
|
|
430
|
+
protected enableRowLevelSecurity(options: {
|
|
431
|
+
readonly schema: string;
|
|
432
|
+
readonly table: string;
|
|
433
|
+
}): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
|
|
434
|
+
return new EnableRowLevelSecurityCall(options.schema, options.table).toOp(
|
|
435
|
+
this.controlAdapterFor('enableRowLevelSecurity'),
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
protected disableRowLevelSecurity(options: {
|
|
440
|
+
readonly schema: string;
|
|
441
|
+
readonly table: string;
|
|
442
|
+
}): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
|
|
443
|
+
return new DisableRowLevelSecurityCall(options.schema, options.table).toOp(
|
|
444
|
+
this.controlAdapterFor('disableRowLevelSecurity'),
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
protected createRlsPolicy(options: {
|
|
449
|
+
readonly schema: string;
|
|
450
|
+
readonly table: string;
|
|
451
|
+
readonly policy: PostgresRlsPolicyInput;
|
|
452
|
+
}): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
|
|
453
|
+
return new CreatePostgresRlsPolicyCall(
|
|
454
|
+
options.schema,
|
|
455
|
+
options.table,
|
|
456
|
+
new PostgresRlsPolicy(options.policy),
|
|
457
|
+
).toOp(this.controlAdapterFor('createRlsPolicy'));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
protected dropRlsPolicy(options: {
|
|
461
|
+
readonly schema: string;
|
|
462
|
+
readonly table: string;
|
|
463
|
+
readonly policy: string;
|
|
464
|
+
}): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
|
|
465
|
+
return new DropPostgresRlsPolicyCall(options.schema, options.table, options.policy).toOp(
|
|
466
|
+
this.controlAdapterFor('dropRlsPolicy'),
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
protected renameRlsPolicy(options: {
|
|
471
|
+
readonly schema: string;
|
|
472
|
+
readonly table: string;
|
|
473
|
+
readonly from: string;
|
|
474
|
+
readonly to: string;
|
|
475
|
+
}): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
|
|
476
|
+
return new RenamePostgresRlsPolicyCall(
|
|
477
|
+
options.schema,
|
|
478
|
+
options.table,
|
|
479
|
+
options.from,
|
|
480
|
+
options.to,
|
|
481
|
+
).toOp(this.controlAdapterFor('renameRlsPolicy'));
|
|
482
|
+
}
|
|
423
483
|
}
|