@prisma-next/target-postgres 0.15.0-dev.8 → 0.15.0
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-policy-UPteqwtU.mjs → control-policy-DtbkLLGv.mjs} +72 -41
- package/dist/control-policy-DtbkLLGv.mjs.map +1 -0
- package/dist/control.mjs +3 -3
- package/dist/{diff-database-schema-47526Pv2.mjs → diff-database-schema-GncqMuSI.mjs} +22 -85
- package/dist/diff-database-schema-GncqMuSI.mjs.map +1 -0
- package/dist/diff-database-schema.d.mts +12 -3
- package/dist/diff-database-schema.d.mts.map +1 -1
- package/dist/diff-database-schema.mjs +1 -1
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +1 -1
- package/dist/{planner-uHcFFWkx.mjs → planner-kql24i_F.mjs} +9 -9
- package/dist/planner-kql24i_F.mjs.map +1 -0
- package/dist/planner.d.mts +1 -1
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +2 -2
- package/dist/{postgres-database-schema-node-DJOTywwQ.d.mts → postgres-database-schema-node-CJv5bfbs.d.mts} +2 -11
- package/dist/postgres-database-schema-node-CJv5bfbs.d.mts.map +1 -0
- package/dist/{postgres-role-schema-node-bg32e7I-.mjs → postgres-role-schema-node-Cm-j0ZGM.mjs} +2 -3
- package/dist/postgres-role-schema-node-Cm-j0ZGM.mjs.map +1 -0
- package/dist/types.d.mts +2 -3
- package/dist/types.mjs +3 -3
- package/package.json +20 -20
- package/src/core/migrations/contract-to-postgres-database-schema-node.ts +19 -99
- package/src/core/migrations/control-policy.ts +3 -4
- package/src/core/migrations/diff-database-schema.ts +3 -3
- package/src/core/migrations/issue-planner.ts +80 -41
- package/src/core/migrations/planner-strategies.ts +6 -6
- package/src/core/migrations/planner.ts +4 -5
- package/src/core/migrations/verify-postgres-namespaces.ts +1 -0
- package/src/core/schema-ir/postgres-policy-schema-node.ts +2 -12
- package/src/exports/types.ts +0 -1
- package/dist/control-policy-UPteqwtU.mjs.map +0 -1
- package/dist/diff-database-schema-47526Pv2.mjs.map +0 -1
- package/dist/planner-uHcFFWkx.mjs.map +0 -1
- package/dist/postgres-database-schema-node-DJOTywwQ.d.mts.map +0 -1
- package/dist/postgres-role-schema-node-bg32e7I-.mjs.map +0 -1
- package/dist/schema-node-kinds-CrYzCR1i.d.mts +0 -30
- package/dist/schema-node-kinds-CrYzCR1i.d.mts.map +0 -1
package/package.json
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/target-postgres",
|
|
3
|
-
"version": "0.15.0
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"description": "Postgres target pack for Prisma Next",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@prisma-next/cli": "0.15.0
|
|
10
|
-
"@prisma-next/contract": "0.15.0
|
|
11
|
-
"@prisma-next/errors": "0.15.0
|
|
12
|
-
"@prisma-next/family-sql": "0.15.0
|
|
13
|
-
"@prisma-next/framework-components": "0.15.0
|
|
14
|
-
"@prisma-next/migration-tools": "0.15.0
|
|
15
|
-
"@prisma-next/psl-parser": "0.15.0
|
|
16
|
-
"@prisma-next/sql-contract": "0.15.0
|
|
17
|
-
"@prisma-next/sql-errors": "0.15.0
|
|
18
|
-
"@prisma-next/sql-operations": "0.15.0
|
|
19
|
-
"@prisma-next/sql-relational-core": "0.15.0
|
|
20
|
-
"@prisma-next/sql-schema-ir": "0.15.0
|
|
21
|
-
"@prisma-next/ts-render": "0.15.0
|
|
22
|
-
"@prisma-next/utils": "0.15.0
|
|
9
|
+
"@prisma-next/cli": "0.15.0",
|
|
10
|
+
"@prisma-next/contract": "0.15.0",
|
|
11
|
+
"@prisma-next/errors": "0.15.0",
|
|
12
|
+
"@prisma-next/family-sql": "0.15.0",
|
|
13
|
+
"@prisma-next/framework-components": "0.15.0",
|
|
14
|
+
"@prisma-next/migration-tools": "0.15.0",
|
|
15
|
+
"@prisma-next/psl-parser": "0.15.0",
|
|
16
|
+
"@prisma-next/sql-contract": "0.15.0",
|
|
17
|
+
"@prisma-next/sql-errors": "0.15.0",
|
|
18
|
+
"@prisma-next/sql-operations": "0.15.0",
|
|
19
|
+
"@prisma-next/sql-relational-core": "0.15.0",
|
|
20
|
+
"@prisma-next/sql-schema-ir": "0.15.0",
|
|
21
|
+
"@prisma-next/ts-render": "0.15.0",
|
|
22
|
+
"@prisma-next/utils": "0.15.0",
|
|
23
23
|
"@standard-schema/spec": "^1.1.0",
|
|
24
24
|
"arktype": "^2.2.2",
|
|
25
25
|
"pathe": "^2.0.3"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@prisma-next/psl-printer": "0.15.0
|
|
29
|
-
"@prisma-next/sql-contract-psl": "0.15.0
|
|
30
|
-
"@prisma-next/test-utils": "0.15.0
|
|
31
|
-
"@prisma-next/tsconfig": "0.15.0
|
|
32
|
-
"@prisma-next/tsdown": "0.15.0
|
|
28
|
+
"@prisma-next/psl-printer": "0.15.0",
|
|
29
|
+
"@prisma-next/sql-contract-psl": "0.15.0",
|
|
30
|
+
"@prisma-next/test-utils": "0.15.0",
|
|
31
|
+
"@prisma-next/tsconfig": "0.15.0",
|
|
32
|
+
"@prisma-next/tsdown": "0.15.0",
|
|
33
33
|
"tsdown": "0.22.3",
|
|
34
34
|
"typescript": "5.9.3",
|
|
35
35
|
"vitest": "4.1.10"
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import type { ContractToSchemaIROptions } from '@prisma-next/family-sql/control';
|
|
2
2
|
import { contractNamespaceToSchemaIR } from '@prisma-next/family-sql/control';
|
|
3
|
-
import type { SchemaNodeRef } from '@prisma-next/framework-components/control';
|
|
4
3
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
5
|
-
import {
|
|
6
|
-
PrimaryKey,
|
|
7
|
-
RelationalSchemaNodeKind,
|
|
8
|
-
SqlForeignKeyIR,
|
|
9
|
-
SqlIndexIR,
|
|
10
|
-
SqlUniqueIR,
|
|
11
|
-
} from '@prisma-next/sql-schema-ir/types';
|
|
4
|
+
import { SqlForeignKeyIR } from '@prisma-next/sql-schema-ir/types';
|
|
12
5
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
13
6
|
import type { PostgresRlsPolicy } from '../postgres-rls-policy';
|
|
14
7
|
import type { PostgresContract } from '../postgres-schema';
|
|
@@ -19,43 +12,8 @@ import { PostgresNativeEnumSchemaNode } from '../schema-ir/postgres-native-enum-
|
|
|
19
12
|
import { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';
|
|
20
13
|
import { PostgresRoleSchemaNode } from '../schema-ir/postgres-role-schema-node';
|
|
21
14
|
import { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';
|
|
22
|
-
import { PostgresSchemaNodeKind } from '../schema-ir/schema-node-kinds';
|
|
23
15
|
import { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';
|
|
24
16
|
|
|
25
|
-
/** The database root's fixed sentinel id (`PostgresDatabaseSchemaNode#id`). */
|
|
26
|
-
function databaseStep(): { readonly nodeKind: string; readonly id: string } {
|
|
27
|
-
return { nodeKind: PostgresSchemaNodeKind.database, id: 'database' };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function tableDependsOn(namespaceId: string, tableName: string): SchemaNodeRef {
|
|
31
|
-
return [
|
|
32
|
-
databaseStep(),
|
|
33
|
-
{ nodeKind: PostgresSchemaNodeKind.namespace, id: namespaceId },
|
|
34
|
-
{ nodeKind: PostgresSchemaNodeKind.table, id: tableName },
|
|
35
|
-
];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function roleDependsOn(role: string): SchemaNodeRef {
|
|
39
|
-
return [databaseStep(), { nodeKind: PostgresSchemaNodeKind.role, id: role }];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* The chains from a table-child object (foreign key, index, unique, primary
|
|
44
|
-
* key) to each of the own columns it is built on, in the Postgres tree.
|
|
45
|
-
* Dropping a covered column auto-drops the object, so the object's drop must
|
|
46
|
-
* precede the column's; the graph derives that direction from these edges.
|
|
47
|
-
*/
|
|
48
|
-
function columnDependsOn(
|
|
49
|
-
namespaceId: string,
|
|
50
|
-
tableName: string,
|
|
51
|
-
columns: readonly string[],
|
|
52
|
-
): readonly SchemaNodeRef[] {
|
|
53
|
-
return columns.map((column) => [
|
|
54
|
-
...tableDependsOn(namespaceId, tableName),
|
|
55
|
-
{ nodeKind: RelationalSchemaNodeKind.column, id: `column:${column}` },
|
|
56
|
-
]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
17
|
function toPolicyNode(policy: PostgresRlsPolicy, namespaceId: string): PostgresPolicySchemaNode {
|
|
60
18
|
return new PostgresPolicySchemaNode({
|
|
61
19
|
name: policy.name,
|
|
@@ -67,7 +25,6 @@ function toPolicyNode(policy: PostgresRlsPolicy, namespaceId: string): PostgresP
|
|
|
67
25
|
...ifDefined('using', policy.using),
|
|
68
26
|
...ifDefined('withCheck', policy.withCheck),
|
|
69
27
|
permissive: policy.permissive,
|
|
70
|
-
dependsOn: [tableDependsOn(namespaceId, policy.tableName), ...policy.roles.map(roleDependsOn)],
|
|
71
28
|
});
|
|
72
29
|
}
|
|
73
30
|
|
|
@@ -158,67 +115,30 @@ export function contractToPostgresDatabaseSchemaNode(
|
|
|
158
115
|
// clauses, and resolves the real live DDL schema — introspected FKs
|
|
159
116
|
// already carry the live schema, so this is what lets an expected FK
|
|
160
117
|
// pair (by diff-node id) with its introspected counterpart.
|
|
161
|
-
const foreignKeys = sqlTable.foreignKeys.map(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
dependsOn: [
|
|
177
|
-
tableDependsOn(resolvedReferencedNamespace, fk.referencedTable),
|
|
178
|
-
...columnDependsOn(ddlSchema, tableName, fk.columns),
|
|
179
|
-
],
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
// The family stamped these children's own-column `dependsOn` with the
|
|
183
|
-
// flat (single-schema) chain; the Postgres tree nests them under a
|
|
184
|
-
// namespace, so re-stamp with the multi-schema chain that matches this
|
|
185
|
-
// tree's paths. Every other field is carried through unchanged.
|
|
186
|
-
const uniques = sqlTable.uniques.map(
|
|
187
|
-
(u) =>
|
|
188
|
-
new SqlUniqueIR({
|
|
189
|
-
columns: u.columns,
|
|
190
|
-
...ifDefined('name', u.name),
|
|
191
|
-
...ifDefined('annotations', u.annotations),
|
|
192
|
-
dependsOn: columnDependsOn(ddlSchema, tableName, u.columns),
|
|
193
|
-
}),
|
|
194
|
-
);
|
|
195
|
-
const indexes = sqlTable.indexes.map(
|
|
196
|
-
(i) =>
|
|
197
|
-
new SqlIndexIR({
|
|
198
|
-
columns: i.columns,
|
|
199
|
-
unique: i.unique,
|
|
200
|
-
...ifDefined('name', i.name),
|
|
201
|
-
...ifDefined('type', i.type),
|
|
202
|
-
...ifDefined('options', i.options),
|
|
203
|
-
...ifDefined('annotations', i.annotations),
|
|
204
|
-
dependsOn: columnDependsOn(ddlSchema, tableName, i.columns),
|
|
118
|
+
const foreignKeys = sqlTable.foreignKeys.map(
|
|
119
|
+
(fk) =>
|
|
120
|
+
new SqlForeignKeyIR({
|
|
121
|
+
columns: fk.columns,
|
|
122
|
+
referencedTable: fk.referencedTable,
|
|
123
|
+
referencedColumns: fk.referencedColumns,
|
|
124
|
+
referencedSchema: fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,
|
|
125
|
+
...ifDefined('name', fk.name),
|
|
126
|
+
...ifDefined('onDelete', fk.onDelete),
|
|
127
|
+
...ifDefined('onUpdate', fk.onUpdate),
|
|
128
|
+
...ifDefined('annotations', fk.annotations),
|
|
129
|
+
resolvedReferencedNamespace: resolveDdlSchemaForNamespaceStorage(
|
|
130
|
+
contract.storage,
|
|
131
|
+
fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,
|
|
132
|
+
),
|
|
205
133
|
}),
|
|
206
134
|
);
|
|
207
|
-
const primaryKey =
|
|
208
|
-
sqlTable.primaryKey !== undefined
|
|
209
|
-
? new PrimaryKey({
|
|
210
|
-
columns: sqlTable.primaryKey.columns,
|
|
211
|
-
...ifDefined('name', sqlTable.primaryKey.name),
|
|
212
|
-
dependsOn: columnDependsOn(ddlSchema, tableName, sqlTable.primaryKey.columns),
|
|
213
|
-
})
|
|
214
|
-
: undefined;
|
|
215
135
|
tables[tableName] = new PostgresTableSchemaNode({
|
|
216
136
|
name: sqlTable.name,
|
|
217
137
|
columns: sqlTable.columns,
|
|
218
138
|
foreignKeys,
|
|
219
|
-
uniques,
|
|
220
|
-
indexes,
|
|
221
|
-
...ifDefined('primaryKey', primaryKey),
|
|
139
|
+
uniques: sqlTable.uniques,
|
|
140
|
+
indexes: sqlTable.indexes,
|
|
141
|
+
...ifDefined('primaryKey', sqlTable.primaryKey),
|
|
222
142
|
...ifDefined('annotations', sqlTable.annotations),
|
|
223
143
|
...ifDefined('checks', sqlTable.checks),
|
|
224
144
|
policies: policiesByTable.get(tableName) ?? [],
|
|
@@ -5,7 +5,6 @@ import type {
|
|
|
5
5
|
SuppressionRecord,
|
|
6
6
|
} from '@prisma-next/family-sql/control';
|
|
7
7
|
import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
|
|
8
|
-
import { issueOutcome } from '@prisma-next/framework-components/control';
|
|
9
8
|
import { entityAt, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
10
9
|
import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types';
|
|
11
10
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
@@ -253,7 +252,7 @@ const POSTGRES_NODE_CREATION_FACTORY: Readonly<Record<string, string>> = Object.
|
|
|
253
252
|
* it is not admitted as creation-class here.
|
|
254
253
|
*/
|
|
255
254
|
function isEnablementCreationIssue(issue: SchemaDiffIssue<SqlSchemaDiffNode>): boolean {
|
|
256
|
-
if (
|
|
255
|
+
if (issue.reason !== 'not-equal') return false;
|
|
257
256
|
const { expected, actual } = issue;
|
|
258
257
|
return (
|
|
259
258
|
expected !== undefined &&
|
|
@@ -271,7 +270,7 @@ export function resolvePostgresNodeIssueCreationFactoryName(
|
|
|
271
270
|
if (isEnablementCreationIssue(issue)) {
|
|
272
271
|
return 'enableRowLevelSecurity';
|
|
273
272
|
}
|
|
274
|
-
if (
|
|
273
|
+
if (issue.reason !== 'not-found') return undefined;
|
|
275
274
|
const node = issue.expected ?? issue.actual;
|
|
276
275
|
if (node === undefined) return undefined;
|
|
277
276
|
return POSTGRES_NODE_CREATION_FACTORY[node.nodeKind];
|
|
@@ -299,7 +298,7 @@ export function resolvePostgresNodeIssueControlPolicySubject(
|
|
|
299
298
|
>(node);
|
|
300
299
|
return {
|
|
301
300
|
namespaceId: resolveNamespaceIdForDdlSchema(contract, namespaceNode.schemaName),
|
|
302
|
-
createsNewObject:
|
|
301
|
+
createsNewObject: issue.reason === 'not-found',
|
|
303
302
|
};
|
|
304
303
|
}
|
|
305
304
|
|
|
@@ -4,7 +4,7 @@ import { buildNativeTypeExpander } from '@prisma-next/family-sql/control';
|
|
|
4
4
|
import { classifyDiffSubjectGranularity } from '@prisma-next/family-sql/diff';
|
|
5
5
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
6
6
|
import type { DiffableNode, SchemaDiffIssue } from '@prisma-next/framework-components/control';
|
|
7
|
-
import { diffSchemas
|
|
7
|
+
import { diffSchemas } from '@prisma-next/framework-components/control';
|
|
8
8
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
9
9
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
10
10
|
import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
|
|
@@ -140,7 +140,7 @@ export function diffPostgresSchema(input: {
|
|
|
140
140
|
const relationalOwned = ownedSchemaNames(expected);
|
|
141
141
|
const structuralOwned = ownedSchemaNames(fullExpected);
|
|
142
142
|
const issues = diffSchemas(expected, actual).filter((issue) => {
|
|
143
|
-
if (
|
|
143
|
+
if (issue.reason !== 'not-expected') return true;
|
|
144
144
|
if (isClusterScopedIssue(issue)) return true;
|
|
145
145
|
const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
|
|
146
146
|
const namespaceSegment = issue.path[1];
|
|
@@ -240,7 +240,7 @@ export function buildPostgresPlanDiff(input: {
|
|
|
240
240
|
readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
|
|
241
241
|
'both trees are PostgresDatabaseSchemaNodes, so every diff-issue node is a SqlSchemaDiffNode'
|
|
242
242
|
>(diffSchemas(expected, paddedActual)).filter((issue) => {
|
|
243
|
-
if (
|
|
243
|
+
if (issue.reason !== 'not-expected') return true;
|
|
244
244
|
if (isClusterScopedIssue(issue)) return true;
|
|
245
245
|
const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
|
|
246
246
|
const namespaceSegment = issue.path[1];
|
|
@@ -25,7 +25,6 @@ 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';
|
|
29
28
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
30
29
|
import type { SqlStorage, StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
31
30
|
import type { DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
|
|
@@ -137,7 +136,6 @@ export interface IssuePlannerValue {
|
|
|
137
136
|
type CallCategory =
|
|
138
137
|
| 'dep'
|
|
139
138
|
| 'drop'
|
|
140
|
-
| 'dropType'
|
|
141
139
|
| 'table'
|
|
142
140
|
| 'rlsEnable'
|
|
143
141
|
| 'rlsPolicy'
|
|
@@ -153,13 +151,6 @@ type CallCategory =
|
|
|
153
151
|
* legacy walk-schema planner's emission order so `db init` and `db update`
|
|
154
152
|
* produce byte-identical plans for the shared shape (deps → drops → tables
|
|
155
153
|
* → 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).
|
|
163
154
|
*/
|
|
164
155
|
function classifyCall(call: PostgresOpFactoryCall): CallCategory {
|
|
165
156
|
switch (call.factoryName) {
|
|
@@ -168,9 +159,8 @@ function classifyCall(call: PostgresOpFactoryCall): CallCategory {
|
|
|
168
159
|
case 'createNativeEnumType':
|
|
169
160
|
case 'addNativeEnumValue':
|
|
170
161
|
return 'dep';
|
|
171
|
-
case 'dropNativeEnumType':
|
|
172
|
-
return 'dropType';
|
|
173
162
|
case 'dropTable':
|
|
163
|
+
case 'dropNativeEnumType':
|
|
174
164
|
case 'dropColumn':
|
|
175
165
|
case 'dropConstraint':
|
|
176
166
|
case 'dropCheckConstraint':
|
|
@@ -379,6 +369,57 @@ export function columnTypeChanged(expected: SqlColumnIR, actual: SqlColumnIR): b
|
|
|
379
369
|
);
|
|
380
370
|
}
|
|
381
371
|
|
|
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
|
+
|
|
382
423
|
// ----------------------------------------------------------------------------
|
|
383
424
|
// Subtree coalescing (the planner's responsibility per the differ's contract)
|
|
384
425
|
// ----------------------------------------------------------------------------
|
|
@@ -396,7 +437,7 @@ export function coalesceSubtreeIssues<TNode extends DiffableNode = DiffableNode>
|
|
|
396
437
|
issues: readonly SchemaDiffIssue<TNode>[],
|
|
397
438
|
): readonly SchemaDiffIssue<TNode>[] {
|
|
398
439
|
const collapsingPaths = issues
|
|
399
|
-
.filter((issue) =>
|
|
440
|
+
.filter((issue) => issue.reason === 'not-found' || issue.reason === 'not-expected')
|
|
400
441
|
.map((issue) => issue.path);
|
|
401
442
|
if (collapsingPaths.length === 0) return issues;
|
|
402
443
|
return issues.filter(
|
|
@@ -560,14 +601,14 @@ function mapNativeEnumNodeIssue(
|
|
|
560
601
|
);
|
|
561
602
|
}
|
|
562
603
|
const schemaName = emissionSchemaName(ctx, ddlSchemaName);
|
|
563
|
-
if (
|
|
604
|
+
if (issue.reason === 'not-found') {
|
|
564
605
|
const expected = blindCast<
|
|
565
606
|
PostgresNativeEnumSchemaNode,
|
|
566
607
|
'a not-found native-enum issue always carries the expected PostgresNativeEnumSchemaNode'
|
|
567
608
|
>(issue.expected);
|
|
568
609
|
return ok([new CreateNativeEnumTypeCall(schemaName, expected.typeName, expected.members)]);
|
|
569
610
|
}
|
|
570
|
-
if (
|
|
611
|
+
if (issue.reason === 'not-expected') {
|
|
571
612
|
const actual = blindCast<
|
|
572
613
|
PostgresNativeEnumSchemaNode,
|
|
573
614
|
'a not-expected native-enum issue always carries the actual PostgresNativeEnumSchemaNode'
|
|
@@ -613,14 +654,14 @@ function mapTableNodeIssue(
|
|
|
613
654
|
ddlSchemaName: string,
|
|
614
655
|
codecHooks: ReadonlyMap<string, CodecControlHooks>,
|
|
615
656
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
616
|
-
if (
|
|
657
|
+
if (issue.reason === 'not-found') {
|
|
617
658
|
const table = blindCast<
|
|
618
659
|
PostgresTableSchemaNode,
|
|
619
660
|
'a not-found table issue always carries the expected PostgresTableSchemaNode'
|
|
620
661
|
>(issue.expected);
|
|
621
662
|
return ok(buildCreateTableCallsFromNode(schemaName, ddlSchemaName, table, codecHooks));
|
|
622
663
|
}
|
|
623
|
-
if (
|
|
664
|
+
if (issue.reason === 'not-expected') {
|
|
624
665
|
const table = blindCast<
|
|
625
666
|
PostgresTableSchemaNode,
|
|
626
667
|
'a not-expected table issue always carries the actual PostgresTableSchemaNode'
|
|
@@ -662,7 +703,7 @@ function mapColumnNodeIssue(
|
|
|
662
703
|
tableName: string,
|
|
663
704
|
codecHooks: ReadonlyMap<string, CodecControlHooks>,
|
|
664
705
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
665
|
-
if (
|
|
706
|
+
if (issue.reason === 'not-found') {
|
|
666
707
|
const column = blindCast<
|
|
667
708
|
SqlColumnIR,
|
|
668
709
|
'a not-found column issue always carries the expected column node'
|
|
@@ -671,7 +712,7 @@ function mapColumnNodeIssue(
|
|
|
671
712
|
new AddColumnCall(schemaName, tableName, renderColumnDdl(column.name, column, codecHooks)),
|
|
672
713
|
]);
|
|
673
714
|
}
|
|
674
|
-
if (
|
|
715
|
+
if (issue.reason === 'not-expected') {
|
|
675
716
|
const column = blindCast<
|
|
676
717
|
SqlColumnIR,
|
|
677
718
|
'a not-expected column issue always carries the actual column node'
|
|
@@ -714,7 +755,7 @@ function mapColumnDefaultNodeIssue(
|
|
|
714
755
|
tableName: string,
|
|
715
756
|
columnName: string,
|
|
716
757
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
717
|
-
if (
|
|
758
|
+
if (issue.reason === 'not-expected') {
|
|
718
759
|
return ok([new DropDefaultCall(schemaName, tableName, columnName)]);
|
|
719
760
|
}
|
|
720
761
|
// not-found (SET DEFAULT, additive) or not-equal (SET DEFAULT, widening).
|
|
@@ -731,7 +772,7 @@ function mapColumnDefaultNodeIssue(
|
|
|
731
772
|
tableName,
|
|
732
773
|
columnName,
|
|
733
774
|
defaultSql,
|
|
734
|
-
|
|
775
|
+
issue.reason === 'not-equal' ? 'widening' : 'additive',
|
|
735
776
|
),
|
|
736
777
|
]);
|
|
737
778
|
}
|
|
@@ -741,7 +782,7 @@ function mapPrimaryKeyNodeIssue(
|
|
|
741
782
|
schemaName: string,
|
|
742
783
|
tableName: string,
|
|
743
784
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
744
|
-
if (
|
|
785
|
+
if (issue.reason === 'not-found') {
|
|
745
786
|
const pk = blindCast<
|
|
746
787
|
{ readonly columns: readonly string[]; readonly name?: string },
|
|
747
788
|
'a not-found primary-key issue always carries the expected PrimaryKey node'
|
|
@@ -749,7 +790,7 @@ function mapPrimaryKeyNodeIssue(
|
|
|
749
790
|
const constraintName = pk.name ?? `${tableName}_pkey`;
|
|
750
791
|
return ok([new AddPrimaryKeyCall(schemaName, tableName, constraintName, [...pk.columns])]);
|
|
751
792
|
}
|
|
752
|
-
if (
|
|
793
|
+
if (issue.reason === 'not-expected') {
|
|
753
794
|
const pk = blindCast<
|
|
754
795
|
{ readonly name?: string },
|
|
755
796
|
'a not-expected primary-key issue always carries the actual PrimaryKey node'
|
|
@@ -766,14 +807,14 @@ function mapForeignKeyNodeIssue(
|
|
|
766
807
|
schemaName: string,
|
|
767
808
|
tableName: string,
|
|
768
809
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
769
|
-
if (
|
|
810
|
+
if (issue.reason === 'not-found') {
|
|
770
811
|
const fk = blindCast<
|
|
771
812
|
SqlForeignKeyIR,
|
|
772
813
|
'a not-found foreign-key issue always carries the expected foreign-key node'
|
|
773
814
|
>(issue.expected);
|
|
774
815
|
return ok([new AddForeignKeyCall(schemaName, tableName, fkSpecFromNode(fk, tableName))]);
|
|
775
816
|
}
|
|
776
|
-
if (
|
|
817
|
+
if (issue.reason === 'not-expected') {
|
|
777
818
|
const fk = blindCast<
|
|
778
819
|
SqlForeignKeyIR,
|
|
779
820
|
'a not-expected foreign-key issue always carries the actual foreign-key node'
|
|
@@ -789,7 +830,7 @@ function mapUniqueNodeIssue(
|
|
|
789
830
|
schemaName: string,
|
|
790
831
|
tableName: string,
|
|
791
832
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
792
|
-
if (
|
|
833
|
+
if (issue.reason === 'not-found') {
|
|
793
834
|
const unique = blindCast<
|
|
794
835
|
SqlUniqueIR,
|
|
795
836
|
'a not-found unique issue always carries the expected unique node'
|
|
@@ -797,7 +838,7 @@ function mapUniqueNodeIssue(
|
|
|
797
838
|
const name = unique.name ?? `${tableName}_${unique.columns.join('_')}_key`;
|
|
798
839
|
return ok([new AddUniqueCall(schemaName, tableName, name, [...unique.columns])]);
|
|
799
840
|
}
|
|
800
|
-
if (
|
|
841
|
+
if (issue.reason === 'not-expected') {
|
|
801
842
|
const unique = blindCast<
|
|
802
843
|
SqlUniqueIR,
|
|
803
844
|
'a not-expected unique issue always carries the actual unique node'
|
|
@@ -813,7 +854,7 @@ function mapIndexNodeIssue(
|
|
|
813
854
|
schemaName: string,
|
|
814
855
|
tableName: string,
|
|
815
856
|
): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
|
|
816
|
-
if (
|
|
857
|
+
if (issue.reason === 'not-found') {
|
|
817
858
|
const index = blindCast<
|
|
818
859
|
SqlIndexIR,
|
|
819
860
|
'a not-found index issue always carries the expected index node'
|
|
@@ -824,7 +865,7 @@ function mapIndexNodeIssue(
|
|
|
824
865
|
if (index.options !== undefined) extras.options = index.options;
|
|
825
866
|
return ok([new CreateIndexCall(schemaName, tableName, indexName, [...index.columns], extras)]);
|
|
826
867
|
}
|
|
827
|
-
if (
|
|
868
|
+
if (issue.reason === 'not-expected') {
|
|
828
869
|
const index = blindCast<
|
|
829
870
|
SqlIndexIR,
|
|
830
871
|
'a not-expected index issue always carries the actual index node'
|
|
@@ -844,7 +885,7 @@ function mapCheckNodeIssue(
|
|
|
844
885
|
// the default mapper handles directly; check_missing / check_mismatch are
|
|
845
886
|
// consumed by `checkConstraintPlanCallStrategy` (drop+recreate), so reaching
|
|
846
887
|
// here for them means the strategy did not run — a conflict.
|
|
847
|
-
if (
|
|
888
|
+
if (issue.reason === 'not-expected') {
|
|
848
889
|
const check = blindCast<
|
|
849
890
|
{ readonly name: string },
|
|
850
891
|
'a not-expected check issue always carries the actual check node'
|
|
@@ -861,8 +902,8 @@ function mapCheckNodeIssue(
|
|
|
861
902
|
|
|
862
903
|
/**
|
|
863
904
|
* Maps one node-typed diff issue to its migration call(s), dispatching on the
|
|
864
|
-
* node's `nodeKind`
|
|
865
|
-
*
|
|
905
|
+
* node's `nodeKind` + `issue.reason`, reading nodes and resolving column DDL
|
|
906
|
+
* from `codecRef` via `column-ddl-rendering.ts`.
|
|
866
907
|
*/
|
|
867
908
|
export function mapNodeIssueToCall(
|
|
868
909
|
issue: SchemaDiffIssue,
|
|
@@ -878,7 +919,7 @@ export function mapNodeIssueToCall(
|
|
|
878
919
|
);
|
|
879
920
|
}
|
|
880
921
|
if (node.nodeKind === PostgresSchemaNodeKind.namespace) {
|
|
881
|
-
if (
|
|
922
|
+
if (issue.reason !== 'not-found') {
|
|
882
923
|
return notOk(
|
|
883
924
|
nodeConflict('unsupportedOperation', `Unexpected namespace drift: ${issue.path.join('/')}`),
|
|
884
925
|
);
|
|
@@ -1011,12 +1052,13 @@ export function planIssues(
|
|
|
1011
1052
|
}
|
|
1012
1053
|
}
|
|
1013
1054
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1055
|
+
const sorted = [...remaining].sort((a, b) => {
|
|
1056
|
+
const kindDelta = nodeIssueOrder(a) - nodeIssueOrder(b);
|
|
1057
|
+
if (kindDelta !== 0) return kindDelta;
|
|
1058
|
+
const keyA = nodeIssueKey(a);
|
|
1059
|
+
const keyB = nodeIssueKey(b);
|
|
1060
|
+
return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;
|
|
1061
|
+
});
|
|
1020
1062
|
|
|
1021
1063
|
const defaultCalls: PostgresOpFactoryCall[] = [];
|
|
1022
1064
|
const conflicts: SqlPlannerConflict[] = [];
|
|
@@ -1064,9 +1106,6 @@ export function planIssues(
|
|
|
1064
1106
|
const calls: PostgresOpFactoryCall[] = [
|
|
1065
1107
|
...byCategory('dep'),
|
|
1066
1108
|
...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'),
|
|
1070
1109
|
...byCategory('table'),
|
|
1071
1110
|
...byCategory('column'),
|
|
1072
1111
|
...gatedRecipe,
|
|
@@ -35,7 +35,6 @@ 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';
|
|
39
38
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
40
39
|
import {
|
|
41
40
|
type SqlStorage,
|
|
@@ -199,7 +198,7 @@ export const notNullBackfillCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
199
198
|
const calls: PostgresOpFactoryCall[] = [];
|
|
200
199
|
|
|
201
200
|
for (const issue of issues) {
|
|
202
|
-
if (
|
|
201
|
+
if (issue.reason !== 'not-found') continue;
|
|
203
202
|
const node = issueNode(issue);
|
|
204
203
|
if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.column) continue;
|
|
205
204
|
const expected = blindCast<
|
|
@@ -264,7 +263,7 @@ export const typeChangeCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
|
264
263
|
const calls: PostgresOpFactoryCall[] = [];
|
|
265
264
|
|
|
266
265
|
for (const issue of issues) {
|
|
267
|
-
if (
|
|
266
|
+
if (issue.reason !== 'not-equal') continue;
|
|
268
267
|
const pair = columnNodePair(issue);
|
|
269
268
|
if (pair === undefined) continue;
|
|
270
269
|
const { expected, actual } = pair;
|
|
@@ -358,7 +357,7 @@ export const nullableTighteningCallStrategy: CallMigrationStrategy = (issues, ct
|
|
|
358
357
|
const calls: PostgresOpFactoryCall[] = [];
|
|
359
358
|
|
|
360
359
|
for (const issue of issues) {
|
|
361
|
-
if (
|
|
360
|
+
if (issue.reason !== 'not-equal') continue;
|
|
362
361
|
const pair = columnNodePair(issue);
|
|
363
362
|
if (pair === undefined) continue;
|
|
364
363
|
const { expected, actual } = pair;
|
|
@@ -591,7 +590,7 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
591
590
|
const mutableStorageTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
|
|
592
591
|
|
|
593
592
|
for (const issue of issues) {
|
|
594
|
-
if (
|
|
593
|
+
if (issue.reason !== 'not-found') continue;
|
|
595
594
|
const node = issueNode(issue);
|
|
596
595
|
if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.column) continue;
|
|
597
596
|
const expected = blindCast<
|
|
@@ -686,7 +685,8 @@ function canUseSharedTemporaryDefaultStrategy(options: {
|
|
|
686
685
|
}
|
|
687
686
|
|
|
688
687
|
for (const foreignKey of table.foreignKeys) {
|
|
689
|
-
if (!foreignKey.source.columns.includes(columnName))
|
|
688
|
+
if (foreignKey.constraint === false || !foreignKey.source.columns.includes(columnName))
|
|
689
|
+
continue;
|
|
690
690
|
if (!schemaLookup || !hasForeignKey(schemaLookup, foreignKey)) return false;
|
|
691
691
|
}
|
|
692
692
|
|