@prisma-next/target-postgres 0.16.0-dev.3 → 0.16.0-dev.31
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/README.md +1 -1
- package/dist/{authoring-34L18kA1.mjs → authoring-DnO1B0N9.mjs} +29 -18
- package/dist/authoring-DnO1B0N9.mjs.map +1 -0
- package/dist/canonicalize-C7zKHw8m.mjs +59 -0
- package/dist/canonicalize-C7zKHw8m.mjs.map +1 -0
- package/dist/{canonicalize-D1F4vUnP.d.mts → canonicalize-CQlxSR4D.d.mts} +3 -18
- package/dist/canonicalize-CQlxSR4D.d.mts.map +1 -0
- package/dist/{codec-types-CQ07nOz-.d.mts → codec-types-DdoPc2dy.d.mts} +3 -2
- package/dist/codec-types-DdoPc2dy.d.mts.map +1 -0
- package/dist/codec-types.d.mts +1 -1
- package/dist/{codecs-G4DTY0F_.d.mts → codecs-CGE5dh94.d.mts} +7 -2
- package/dist/codecs-CGE5dh94.d.mts.map +1 -0
- package/dist/{codecs-TCsGgW-Y.mjs → codecs-Deand7sy.mjs} +84 -26
- package/dist/codecs-Deand7sy.mjs.map +1 -0
- package/dist/codecs.d.mts +2 -2
- package/dist/codecs.mjs +2 -2
- package/dist/contract-free.d.mts +1 -1
- package/dist/contract-free.d.mts.map +1 -1
- package/dist/contract-free.mjs +2 -2
- package/dist/{control-policy-CZKdMztu.mjs → control-policy-DEwdS3oK.mjs} +29 -29
- package/dist/control-policy-DEwdS3oK.mjs.map +1 -0
- package/dist/control.mjs +91 -239
- package/dist/control.mjs.map +1 -1
- package/dist/data-transform-BOWpliq8.mjs.map +1 -1
- package/dist/{ddl-DHXKzQ99.mjs → ddl-BQC6u-L9.mjs} +28 -4
- package/dist/{ddl-DHXKzQ99.mjs.map → ddl-BQC6u-L9.mjs.map} +1 -1
- package/dist/ddl.d.mts +3 -3
- package/dist/ddl.mjs +2 -2
- package/dist/{descriptor-meta-qubYaxQA.mjs → descriptor-meta-DGlJurgA.mjs} +4 -4
- package/dist/{descriptor-meta-qubYaxQA.mjs.map → descriptor-meta-DGlJurgA.mjs.map} +1 -1
- package/dist/{diff-database-schema-C-hnJ5mr.mjs → diff-database-schema-qRPwuYBH.mjs} +37 -15
- package/dist/diff-database-schema-qRPwuYBH.mjs.map +1 -0
- package/dist/diff-database-schema.mjs +1 -1
- package/dist/{errors-ByDS0xJL.mjs → errors-DBSNx7EJ.mjs} +6 -2
- package/dist/errors-DBSNx7EJ.mjs.map +1 -0
- package/dist/errors.d.mts +4 -1
- package/dist/errors.d.mts.map +1 -1
- package/dist/errors.mjs +2 -2
- package/dist/issue-planner.d.mts +2 -2
- 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/{nodes-DJC5B8Id.mjs → nodes-04EILM-z.mjs} +65 -2
- package/dist/nodes-04EILM-z.mjs.map +1 -0
- package/dist/{nodes-CQzM2hR0.d.mts → nodes-DjCsN-uv.d.mts} +69 -4
- package/dist/nodes-DjCsN-uv.d.mts.map +1 -0
- package/dist/{op-factory-call-DT7kdjVP.d.mts → op-factory-call-0TB5XNij.d.mts} +46 -8
- package/dist/op-factory-call-0TB5XNij.d.mts.map +1 -0
- package/dist/{op-factory-call-DuRj7WXX.mjs → op-factory-call-CIaPDDUc.mjs} +147 -64
- package/dist/op-factory-call-CIaPDDUc.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 +2 -2
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/{planner-DPGxFcXo.mjs → planner-BhnkIJX-.mjs} +121 -13
- package/dist/planner-BhnkIJX-.mjs.map +1 -0
- package/dist/{planner-ddl-builders-BKlmGDJl.mjs → planner-ddl-builders-CVo3L7Nd.mjs} +12 -7
- package/dist/planner-ddl-builders-CVo3L7Nd.mjs.map +1 -0
- package/dist/planner-ddl-builders.d.mts.map +1 -1
- package/dist/planner-ddl-builders.mjs +1 -1
- package/dist/{planner-produced-postgres-migration-CFDpqWWE.mjs → planner-produced-postgres-migration-BE3EJIRK.mjs} +9 -6
- package/dist/planner-produced-postgres-migration-BE3EJIRK.mjs.map +1 -0
- package/dist/{planner-produced-postgres-migration-C-dwGdb2.d.mts → planner-produced-postgres-migration-CRAHl6ak.d.mts} +3 -3
- package/dist/planner-produced-postgres-migration-CRAHl6ak.d.mts.map +1 -0
- package/dist/planner-produced-postgres-migration.d.mts +1 -1
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/{planner-schema-lookup-CiVaAQP-.mjs → planner-schema-lookup-D3Y_OL-C.mjs} +3 -3
- package/dist/planner-schema-lookup-D3Y_OL-C.mjs.map +1 -0
- package/dist/planner-schema-lookup.mjs +1 -1
- package/dist/{planner-sql-checks-BuB877wH.mjs → planner-sql-checks-CmiQ6Nei.mjs} +3 -3
- package/dist/{planner-sql-checks-BuB877wH.mjs.map → planner-sql-checks-CmiQ6Nei.mjs.map} +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/planner.d.mts +27 -1
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +2 -2
- package/dist/{postgres-contract-view-DebHMqGR.mjs → postgres-contract-view-BeIEyZfD.mjs} +3 -3
- package/dist/{postgres-contract-view-DebHMqGR.mjs.map → postgres-contract-view-BeIEyZfD.mjs.map} +1 -1
- package/dist/{postgres-migration-C_OPFhIF.mjs → postgres-migration--BzvpA92.mjs} +9 -6
- package/dist/postgres-migration--BzvpA92.mjs.map +1 -0
- package/dist/{postgres-migration-DLIvzHjX.d.mts → postgres-migration-BXelnqHO.d.mts} +15 -10
- package/dist/postgres-migration-BXelnqHO.d.mts.map +1 -0
- package/dist/{postgres-schema-CewLYHB7.mjs → postgres-schema-CTplQZDK.mjs} +2 -2
- package/dist/{postgres-schema-CewLYHB7.mjs.map → postgres-schema-CTplQZDK.mjs.map} +1 -1
- package/dist/{render-ops-BREh1kHe.mjs → render-ops-TcY9XEkz.mjs} +7 -2
- package/dist/render-ops-TcY9XEkz.mjs.map +1 -0
- package/dist/render-ops.d.mts.map +1 -1
- package/dist/render-ops.mjs +1 -1
- package/dist/{render-typescript-7yqILcwr.mjs → render-typescript-Ta7_6CGf.mjs} +20 -8
- package/dist/render-typescript-Ta7_6CGf.mjs.map +1 -0
- package/dist/render-typescript.d.mts +2 -0
- package/dist/render-typescript.d.mts.map +1 -1
- package/dist/render-typescript.mjs +1 -1
- package/dist/{resolve-ddl-schema-D0wi_P9Q.mjs → resolve-ddl-schema-BAsJEKW8.mjs} +2 -2
- package/dist/{resolve-ddl-schema-D0wi_P9Q.mjs.map → resolve-ddl-schema-BAsJEKW8.mjs.map} +1 -1
- package/dist/rls-canonicalize.d.mts +2 -26
- package/dist/rls-canonicalize.mjs +2 -2
- package/dist/runtime.mjs +1 -1
- package/dist/schema-ir-annotations.mjs +1 -1
- package/dist/{sql-utils-SU4FDvIV.mjs → sql-utils-B2lzkKfP.mjs} +22 -19
- package/dist/sql-utils-B2lzkKfP.mjs.map +1 -0
- package/dist/sql-utils.d.mts +4 -9
- package/dist/sql-utils.d.mts.map +1 -1
- package/dist/sql-utils.mjs +2 -2
- package/dist/types.mjs +1 -1
- package/package.json +20 -20
- package/src/contract-free/ddl.ts +47 -0
- package/src/core/authoring.ts +35 -26
- package/src/core/codec-helpers.ts +69 -16
- package/src/core/codec-type-map.ts +2 -0
- package/src/core/codecs.ts +29 -14
- package/src/core/ddl/nodes.ts +104 -0
- package/src/core/errors.ts +30 -0
- package/src/core/migrations/column-ddl-rendering.ts +2 -1
- package/src/core/migrations/contract-to-postgres-database-schema-node.ts +38 -14
- package/src/core/migrations/issue-planner.ts +40 -13
- package/src/core/migrations/op-factory-call.ts +189 -44
- package/src/core/migrations/operations/constraints.ts +3 -2
- package/src/core/migrations/operations/data-transform.ts +1 -1
- package/src/core/migrations/operations/indexes.ts +80 -32
- package/src/core/migrations/operations/rls.ts +4 -1
- package/src/core/migrations/planner-ddl-builders.ts +16 -6
- package/src/core/migrations/planner-produced-postgres-migration.ts +8 -1
- package/src/core/migrations/planner-schema-lookup.ts +2 -2
- package/src/core/migrations/planner.ts +163 -5
- package/src/core/migrations/postgres-migration.ts +26 -8
- package/src/core/migrations/render-ops.ts +4 -1
- package/src/core/migrations/render-typescript.ts +20 -8
- package/src/core/migrations/runner.ts +7 -3
- package/src/core/psl-infer/infer-psl-contract.ts +36 -185
- package/src/core/psl-infer/postgres-type-map.ts +43 -65
- package/src/core/rls/canonicalize.ts +4 -21
- package/src/core/sql-utils.ts +16 -27
- package/src/exports/codecs.ts +2 -0
- package/src/exports/ddl.ts +4 -0
- package/src/exports/errors.ts +1 -1
- package/src/exports/op-factory-call.ts +1 -0
- package/src/exports/rls-canonicalize.ts +0 -6
- package/src/exports/sql-utils.ts +0 -1
- package/dist/authoring-34L18kA1.mjs.map +0 -1
- package/dist/canonicalize-D1F4vUnP.d.mts.map +0 -1
- package/dist/codec-types-CQ07nOz-.d.mts.map +0 -1
- package/dist/codecs-G4DTY0F_.d.mts.map +0 -1
- package/dist/codecs-TCsGgW-Y.mjs.map +0 -1
- package/dist/control-policy-CZKdMztu.mjs.map +0 -1
- package/dist/diff-database-schema-C-hnJ5mr.mjs.map +0 -1
- package/dist/errors-ByDS0xJL.mjs.map +0 -1
- package/dist/nodes-CQzM2hR0.d.mts.map +0 -1
- package/dist/nodes-DJC5B8Id.mjs.map +0 -1
- package/dist/op-factory-call-DT7kdjVP.d.mts.map +0 -1
- package/dist/op-factory-call-DuRj7WXX.mjs.map +0 -1
- package/dist/planner-DPGxFcXo.mjs.map +0 -1
- package/dist/planner-ddl-builders-BKlmGDJl.mjs.map +0 -1
- package/dist/planner-produced-postgres-migration-C-dwGdb2.d.mts.map +0 -1
- package/dist/planner-produced-postgres-migration-CFDpqWWE.mjs.map +0 -1
- package/dist/planner-schema-lookup-CiVaAQP-.mjs.map +0 -1
- package/dist/postgres-migration-C_OPFhIF.mjs.map +0 -1
- package/dist/postgres-migration-DLIvzHjX.d.mts.map +0 -1
- package/dist/render-ops-BREh1kHe.mjs.map +0 -1
- package/dist/render-typescript-7yqILcwr.mjs.map +0 -1
- package/dist/rls-canonicalize.d.mts.map +0 -1
- package/dist/sql-utils-SU4FDvIV.mjs.map +0 -1
- package/dist/wire-name-DbQ9-2RC.mjs +0 -104
- package/dist/wire-name-DbQ9-2RC.mjs.map +0 -1
- package/src/core/rls/wire-name.ts +0 -33
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
|
|
2
|
+
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
2
3
|
import { indexExistsAst } from '../../../contract-free/checks';
|
|
3
|
-
import {
|
|
4
|
-
|
|
4
|
+
import {
|
|
5
|
+
alterIndexRename as alterIndexRenameDdl,
|
|
6
|
+
createIndex as createIndexDdl,
|
|
7
|
+
dropIndex as dropIndexDdl,
|
|
8
|
+
} from '../../../contract-free/ddl';
|
|
5
9
|
import { type Op, step, targetDetails } from './shared';
|
|
6
10
|
|
|
7
11
|
type CheckStep = { sql: string; params?: readonly unknown[] };
|
|
@@ -17,40 +21,51 @@ async function indexExistsSteps(
|
|
|
17
21
|
return { present, absent };
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
/** An unbound-namespace object renders unqualified: the DDL node takes no schema. */
|
|
25
|
+
function ddlSchemaOf(schemaName: string): string | undefined {
|
|
26
|
+
return schemaName === UNBOUND_NAMESPACE_ID ? undefined : schemaName;
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
export interface CreateIndexExtras {
|
|
21
30
|
readonly type?: string;
|
|
22
31
|
readonly options?: Record<string, unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Partial-index predicate (WHERE body, without the keyword). Inserted
|
|
34
|
+
* verbatim, never quoted or escaped — the same opaque-SQL stance as RLS
|
|
35
|
+
* policy predicates.
|
|
36
|
+
*/
|
|
37
|
+
readonly where?: string;
|
|
38
|
+
readonly unique?: boolean;
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function renderIndexOptions(options: Record<string, unknown>): string {
|
|
35
|
-
return Object.entries(options)
|
|
36
|
-
.map(([key, value]) => `${quoteIdentifier(key)} = ${renderIndexOptionValue(key, value)}`)
|
|
37
|
-
.join(', ');
|
|
38
|
-
}
|
|
41
|
+
/**
|
|
42
|
+
* The element list between the parens of CREATE INDEX: either a column
|
|
43
|
+
* tuple (each identifier quoted) or one opaque expression string covering
|
|
44
|
+
* the entire list, inserted verbatim.
|
|
45
|
+
*/
|
|
46
|
+
export type CreateIndexElements =
|
|
47
|
+
| { readonly columns: readonly string[] }
|
|
48
|
+
| { readonly expression: string };
|
|
39
49
|
|
|
40
50
|
export async function createIndex(
|
|
41
51
|
schemaName: string,
|
|
42
52
|
tableName: string,
|
|
43
53
|
indexName: string,
|
|
44
|
-
|
|
54
|
+
elements: CreateIndexElements,
|
|
45
55
|
lowerer: ExecuteRequestLowerer,
|
|
46
56
|
extras?: CreateIndexExtras,
|
|
47
57
|
): Promise<Op> {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
const ddlNode = createIndexDdl({
|
|
59
|
+
schema: ddlSchemaOf(schemaName),
|
|
60
|
+
table: tableName,
|
|
61
|
+
name: indexName,
|
|
62
|
+
unique: extras?.unique === true,
|
|
63
|
+
elements,
|
|
64
|
+
type: extras?.type,
|
|
65
|
+
options: extras?.options,
|
|
66
|
+
where: extras?.where,
|
|
67
|
+
});
|
|
68
|
+
const execute = await lowerer.lowerToExecuteRequest(ddlNode);
|
|
54
69
|
const { present, absent } = await indexExistsSteps(lowerer, schemaName, indexName);
|
|
55
70
|
return {
|
|
56
71
|
id: `index.${tableName}.${indexName}`,
|
|
@@ -58,22 +73,57 @@ export async function createIndex(
|
|
|
58
73
|
operationClass: 'additive',
|
|
59
74
|
target: targetDetails('index', indexName, schemaName, tableName),
|
|
60
75
|
precheck: [step(`ensure index "${indexName}" does not exist`, absent.sql, absent.params)],
|
|
61
|
-
execute: [
|
|
62
|
-
step(
|
|
63
|
-
`create index "${indexName}"`,
|
|
64
|
-
`CREATE INDEX ${quoteIdentifier(indexName)} ON ${qualified}${using} (${columnList})${withClause}`,
|
|
65
|
-
),
|
|
66
|
-
],
|
|
76
|
+
execute: [step(`create index "${indexName}"`, execute.sql, execute.params)],
|
|
67
77
|
postcheck: [step(`verify index "${indexName}" exists`, present.sql, present.params)],
|
|
68
78
|
};
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
/**
|
|
82
|
+
* `ALTER INDEX … RENAME TO`. `widening` for the same typology reason as the
|
|
83
|
+
* RLS policy rename: a rename is neither additive creation nor destructive,
|
|
84
|
+
* and the class vocabulary has no neutral middle class — it is NOT that a
|
|
85
|
+
* rename widens anything.
|
|
86
|
+
*/
|
|
87
|
+
export async function renameIndex(
|
|
88
|
+
schemaName: string,
|
|
89
|
+
tableName: string,
|
|
90
|
+
fromName: string,
|
|
91
|
+
toName: string,
|
|
92
|
+
lowerer: ExecuteRequestLowerer,
|
|
93
|
+
): Promise<Op> {
|
|
94
|
+
const fromChecks = indexExistsAst(schemaName, fromName);
|
|
95
|
+
const toChecks = indexExistsAst(schemaName, toName);
|
|
96
|
+
const fromPresent = await lowerer.lowerToExecuteRequest(fromChecks.indexPresent());
|
|
97
|
+
const toAbsent = await lowerer.lowerToExecuteRequest(toChecks.indexAbsent());
|
|
98
|
+
const toPresent = await lowerer.lowerToExecuteRequest(toChecks.indexPresent());
|
|
99
|
+
const ddlNode = alterIndexRenameDdl({
|
|
100
|
+
schema: ddlSchemaOf(schemaName),
|
|
101
|
+
from: fromName,
|
|
102
|
+
to: toName,
|
|
103
|
+
});
|
|
104
|
+
const execute = await lowerer.lowerToExecuteRequest(ddlNode);
|
|
105
|
+
return {
|
|
106
|
+
id: `index.${schemaName}.${tableName}.${fromName}.rename`,
|
|
107
|
+
label: `Rename index "${fromName}" to "${toName}" on "${tableName}"`,
|
|
108
|
+
operationClass: 'widening',
|
|
109
|
+
target: targetDetails('index', toName, schemaName, tableName),
|
|
110
|
+
precheck: [
|
|
111
|
+
step(`ensure index "${fromName}" exists`, fromPresent.sql, fromPresent.params),
|
|
112
|
+
step(`ensure index "${toName}" does not exist`, toAbsent.sql, toAbsent.params),
|
|
113
|
+
],
|
|
114
|
+
execute: [step(`rename index "${fromName}" to "${toName}"`, execute.sql, execute.params)],
|
|
115
|
+
postcheck: [step(`verify index "${toName}" exists`, toPresent.sql, toPresent.params)],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
71
119
|
export async function dropIndex(
|
|
72
120
|
schemaName: string,
|
|
73
121
|
tableName: string,
|
|
74
122
|
indexName: string,
|
|
75
123
|
lowerer: ExecuteRequestLowerer,
|
|
76
124
|
): Promise<Op> {
|
|
125
|
+
const ddlNode = dropIndexDdl({ schema: ddlSchemaOf(schemaName), name: indexName });
|
|
126
|
+
const execute = await lowerer.lowerToExecuteRequest(ddlNode);
|
|
77
127
|
const { present, absent } = await indexExistsSteps(lowerer, schemaName, indexName);
|
|
78
128
|
return {
|
|
79
129
|
id: `dropIndex.${tableName}.${indexName}`,
|
|
@@ -81,9 +131,7 @@ export async function dropIndex(
|
|
|
81
131
|
operationClass: 'destructive',
|
|
82
132
|
target: targetDetails('index', indexName, schemaName, tableName),
|
|
83
133
|
precheck: [step(`ensure index "${indexName}" exists`, present.sql, present.params)],
|
|
84
|
-
execute: [
|
|
85
|
-
step(`drop index "${indexName}"`, `DROP INDEX ${qualifyTableName(schemaName, indexName)}`),
|
|
86
|
-
],
|
|
134
|
+
execute: [step(`drop index "${indexName}"`, execute.sql, execute.params)],
|
|
87
135
|
postcheck: [step(`verify index "${indexName}" does not exist`, absent.sql, absent.params)],
|
|
88
136
|
};
|
|
89
137
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
disableRowLevelSecurity as disableRowLevelSecurityDdl,
|
|
8
8
|
dropPolicy,
|
|
9
9
|
} from '../../../contract-free/ddl';
|
|
10
|
+
import { postgresError } from '../../errors';
|
|
10
11
|
import type { PostgresRlsPolicy } from '../../postgres-rls-policy';
|
|
11
12
|
import { qualifyTableName } from '../planner-sql-checks';
|
|
12
13
|
import { type Op, step, targetDetails } from './shared';
|
|
@@ -15,8 +16,10 @@ const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
|
15
16
|
|
|
16
17
|
function validateRoleName(role: string): string {
|
|
17
18
|
if (!PLAIN_IDENTIFIER.test(role)) {
|
|
18
|
-
throw
|
|
19
|
+
throw postgresError(
|
|
20
|
+
'CONTRACT.ROLE_INVALID',
|
|
19
21
|
`Invalid role name ${JSON.stringify(role)}: role names must be plain SQL identifiers matching ^[A-Za-z_][A-Za-z0-9_$]*$`,
|
|
22
|
+
{ meta: { role, reason: 'not-a-plain-identifier' } },
|
|
20
23
|
);
|
|
21
24
|
}
|
|
22
25
|
return role;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CodecControlHooks } from '@prisma-next/family-sql/control';
|
|
2
2
|
import type { StorageColumn, StorageTypeInstance } from '@prisma-next/sql-contract/types';
|
|
3
3
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
4
|
+
import { isPgEnumParams } from '../codecs';
|
|
5
|
+
import { postgresError } from '../errors';
|
|
4
6
|
import { escapeLiteral, quoteIdentifier, quoteQualifiedName } from '../sql-utils';
|
|
5
7
|
import type { PostgresColumnDefault } from '../types';
|
|
6
8
|
import { resolveColumnTypeMetadata } from './planner-type-resolution';
|
|
@@ -14,9 +16,11 @@ const SAFE_NATIVE_TYPE_PATTERN = /^[a-zA-Z][a-zA-Z0-9_ ]*(\[\])?$/;
|
|
|
14
16
|
|
|
15
17
|
function assertSafeNativeType(nativeType: string): void {
|
|
16
18
|
if (!SAFE_NATIVE_TYPE_PATTERN.test(nativeType)) {
|
|
17
|
-
throw
|
|
19
|
+
throw postgresError(
|
|
20
|
+
'CONTRACT.NATIVE_TYPE_INVALID',
|
|
18
21
|
`Unsafe native type name in contract: "${nativeType}". ` +
|
|
19
22
|
'Native type names must match /^[a-zA-Z][a-zA-Z0-9_ ]*(\\[\\])?$/',
|
|
23
|
+
{ meta: { nativeType } },
|
|
20
24
|
);
|
|
21
25
|
}
|
|
22
26
|
}
|
|
@@ -28,9 +32,11 @@ function assertSafeNativeType(nativeType: string): void {
|
|
|
28
32
|
*/
|
|
29
33
|
function assertSafeDefaultExpression(expression: string): void {
|
|
30
34
|
if (expression.includes(';') || /--|\/\*|\$\$|\bSELECT\b/i.test(expression)) {
|
|
31
|
-
throw
|
|
35
|
+
throw postgresError(
|
|
36
|
+
'CONTRACT.DEFAULT_INVALID',
|
|
32
37
|
`Unsafe default expression in contract: "${expression}". ` +
|
|
33
38
|
'Default expressions must not contain semicolons, SQL comment tokens, dollar-quoting, or subqueries.',
|
|
39
|
+
{ meta: { expression } },
|
|
34
40
|
);
|
|
35
41
|
}
|
|
36
42
|
}
|
|
@@ -70,7 +76,7 @@ export function buildColumnTypeSql(
|
|
|
70
76
|
// as its quoted, schema-qualified type-name identifier. DDL-render only — the
|
|
71
77
|
// verify comparison value (`resolvedNativeType`) stays the bare name the
|
|
72
78
|
// family expander produces, matching introspection.
|
|
73
|
-
if (
|
|
79
|
+
if (isPgEnumParams(resolved.typeParams)) {
|
|
74
80
|
const quoted = quoteQualifiedName(resolved.nativeType);
|
|
75
81
|
return column.many ? `${quoted}[]` : quoted;
|
|
76
82
|
}
|
|
@@ -98,9 +104,11 @@ function expandParameterizedTypeSql(
|
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
if (!column.codecId) {
|
|
101
|
-
throw
|
|
107
|
+
throw postgresError(
|
|
108
|
+
'CONTRACT.CODEC_DESCRIPTOR_MISSING',
|
|
102
109
|
`Column declares typeParams for nativeType "${column.nativeType}" but has no codecId. ` +
|
|
103
110
|
'Ensure the column is associated with a codec.',
|
|
111
|
+
{ meta: { nativeType: column.nativeType } },
|
|
104
112
|
);
|
|
105
113
|
}
|
|
106
114
|
|
|
@@ -109,10 +117,12 @@ function expandParameterizedTypeSql(
|
|
|
109
117
|
if (hooks?.planTypeOperations) {
|
|
110
118
|
return null;
|
|
111
119
|
}
|
|
112
|
-
throw
|
|
120
|
+
throw postgresError(
|
|
121
|
+
'CONTRACT.PACK_CONTRIBUTION_INVALID',
|
|
113
122
|
`Column declares typeParams for nativeType "${column.nativeType}" ` +
|
|
114
123
|
`but no expandNativeType hook is registered for codecId "${column.codecId}". ` +
|
|
115
|
-
'Ensure the extension providing this codec is included in
|
|
124
|
+
'Ensure the extension providing this codec is included in extensions.',
|
|
125
|
+
{ meta: { codecId: column.codecId, nativeType: column.nativeType } },
|
|
116
126
|
);
|
|
117
127
|
}
|
|
118
128
|
|
|
@@ -42,6 +42,7 @@ export class TypeScriptRenderablePostgresMigration
|
|
|
42
42
|
readonly #calls: readonly OpFactoryCall[];
|
|
43
43
|
readonly #meta: MigrationMeta;
|
|
44
44
|
readonly #spaceId: string;
|
|
45
|
+
readonly #snapshotsImportPath: string;
|
|
45
46
|
readonly #lowerer: ExecuteRequestLowerer | undefined;
|
|
46
47
|
#operationsCache:
|
|
47
48
|
| readonly (
|
|
@@ -54,12 +55,14 @@ export class TypeScriptRenderablePostgresMigration
|
|
|
54
55
|
calls: readonly OpFactoryCall[],
|
|
55
56
|
meta: MigrationMeta,
|
|
56
57
|
spaceId: string,
|
|
58
|
+
snapshotsImportPath: string,
|
|
57
59
|
lowerer?: ExecuteRequestLowerer,
|
|
58
60
|
) {
|
|
59
61
|
super();
|
|
60
62
|
this.#calls = calls;
|
|
61
63
|
this.#meta = meta;
|
|
62
64
|
this.#spaceId = spaceId;
|
|
65
|
+
this.#snapshotsImportPath = snapshotsImportPath;
|
|
63
66
|
this.#lowerer = lowerer;
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -85,6 +88,10 @@ export class TypeScriptRenderablePostgresMigration
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
renderTypeScript(): string {
|
|
88
|
-
return renderCallsToTypeScript(this.#calls, {
|
|
91
|
+
return renderCallsToTypeScript(this.#calls, {
|
|
92
|
+
from: this.#meta.from,
|
|
93
|
+
to: this.#meta.to,
|
|
94
|
+
snapshotsImportPath: this.#snapshotsImportPath,
|
|
95
|
+
});
|
|
89
96
|
}
|
|
90
97
|
}
|
|
@@ -23,9 +23,9 @@ export function buildSchemaLookupMap(schema: SqlSchemaIR): ReadonlyMap<string, S
|
|
|
23
23
|
|
|
24
24
|
function buildSchemaTableLookup(table: SqlSchemaIR['tables'][string]): SchemaTableLookup {
|
|
25
25
|
const uniqueKeys = new Set(table.uniques.map((u) => u.columns.join(',')));
|
|
26
|
-
const indexKeys = new Set(table.indexes.map((i) => i.columns.join(',')));
|
|
26
|
+
const indexKeys = new Set(table.indexes.map((i) => (i.columns ?? []).join(',')));
|
|
27
27
|
const uniqueIndexKeys = new Set(
|
|
28
|
-
table.indexes.filter((i) => i.unique).map((i) => i.columns.join(',')),
|
|
28
|
+
table.indexes.filter((i) => i.unique).map((i) => (i.columns ?? []).join(',')),
|
|
29
29
|
);
|
|
30
30
|
const fkKeys = new Set<string>();
|
|
31
31
|
for (const fk of table.foreignKeys) {
|
|
@@ -24,11 +24,12 @@ import type {
|
|
|
24
24
|
import { issueOutcome } from '@prisma-next/framework-components/control';
|
|
25
25
|
import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
26
26
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
27
|
+
import { parseWireName } from '@prisma-next/sql-schema-ir/naming';
|
|
27
28
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
29
|
+
import { SqlIndexIR } from '@prisma-next/sql-schema-ir/types';
|
|
28
30
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
29
31
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
30
32
|
import { PostgresRlsPolicy } from '../postgres-rls-policy';
|
|
31
|
-
import { parseRlsPolicyWireName } from '../rls/wire-name';
|
|
32
33
|
import { postgresNodeStorageCoordinate } from '../schema-ir/node-storage-coordinate';
|
|
33
34
|
import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
|
|
34
35
|
import { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';
|
|
@@ -46,6 +47,7 @@ import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
|
46
47
|
import {
|
|
47
48
|
CreatePostgresRlsPolicyCall,
|
|
48
49
|
DropPostgresRlsPolicyCall,
|
|
50
|
+
RenameIndexCall,
|
|
49
51
|
RenamePostgresRlsPolicyCall,
|
|
50
52
|
} from './op-factory-call';
|
|
51
53
|
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
@@ -141,6 +143,12 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
141
143
|
* {@link SqlMigrationPlannerPlanOptions.ownership}.
|
|
142
144
|
*/
|
|
143
145
|
readonly ownership?: SchemaOwnership;
|
|
146
|
+
/**
|
|
147
|
+
* POSIX-relative path from the migration package dir to
|
|
148
|
+
* `migrations/snapshots` — see
|
|
149
|
+
* {@link SqlMigrationPlannerPlanOptions.snapshotsImportPath}.
|
|
150
|
+
*/
|
|
151
|
+
readonly snapshotsImportPath: string;
|
|
144
152
|
}): PostgresPlanResult {
|
|
145
153
|
return this.planSql(options as SqlMigrationPlannerPlanOptions);
|
|
146
154
|
}
|
|
@@ -156,6 +164,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
156
164
|
to: context.toHash,
|
|
157
165
|
},
|
|
158
166
|
spaceId,
|
|
167
|
+
context.snapshotsImportPath,
|
|
159
168
|
this.#lowerer,
|
|
160
169
|
);
|
|
161
170
|
}
|
|
@@ -229,6 +238,18 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
229
238
|
});
|
|
230
239
|
const schemaIssues = [...namespaceIssues, ...gated];
|
|
231
240
|
|
|
241
|
+
// Index rename post-pass (the policy pass's structure, generalized): a
|
|
242
|
+
// `not-found` and a `not-expected` index that are one rename collapse
|
|
243
|
+
// into a single widening `ALTER INDEX … RENAME TO` before the per-issue
|
|
244
|
+
// mapping turns them into create + drop. Consumed issues never reach
|
|
245
|
+
// `planIssues`; the rename calls go through the same call-side
|
|
246
|
+
// control-policy partition the policy ops use.
|
|
247
|
+
const indexRenames = this.pairIndexRenames(options, schemaIssues);
|
|
248
|
+
const plannableIssues =
|
|
249
|
+
indexRenames.consumed.size === 0
|
|
250
|
+
? schemaIssues
|
|
251
|
+
: schemaIssues.filter((issue) => !indexRenames.consumed.has(issue));
|
|
252
|
+
|
|
232
253
|
const codecHooks = extractCodecControlHooks(options.frameworkComponents);
|
|
233
254
|
const storageTypes = options.contract.storage.types ?? {};
|
|
234
255
|
// The strategy layer reads the live schema by bare table name for existence
|
|
@@ -246,7 +267,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
246
267
|
// user-visible message survives even when the planner would have failed
|
|
247
268
|
// to model the subject's live shape.
|
|
248
269
|
const issuePartition = partitionIssuesByControlPolicy({
|
|
249
|
-
issues:
|
|
270
|
+
issues: plannableIssues,
|
|
250
271
|
contract: options.contract,
|
|
251
272
|
resolveControlPolicySubject: (issue) =>
|
|
252
273
|
resolvePostgresNodeIssueControlPolicySubject(issue, options.contract),
|
|
@@ -275,6 +296,14 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
275
296
|
return plannerFailure(result.failure);
|
|
276
297
|
}
|
|
277
298
|
|
|
299
|
+
const indexRenamePartition = partitionCallsByControlPolicy({
|
|
300
|
+
calls: indexRenames.calls,
|
|
301
|
+
contract: options.contract,
|
|
302
|
+
resolveControlPolicySubject: (call) =>
|
|
303
|
+
resolvePostgresCallControlPolicySubject(call, options.contract),
|
|
304
|
+
resolveFactoryName: (call) => call.factoryName,
|
|
305
|
+
});
|
|
306
|
+
|
|
278
307
|
const schemaDiffCalls = this.planPostgresSchemaDiff(options, policyDiffIssues);
|
|
279
308
|
const schemaDiffPartition = partitionCallsByControlPolicy({
|
|
280
309
|
calls: schemaDiffCalls,
|
|
@@ -307,9 +336,15 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
307
336
|
resolvePostgresCallControlPolicySubject(call, options.contract),
|
|
308
337
|
resolveFactoryName: (call) => call.factoryName,
|
|
309
338
|
});
|
|
310
|
-
const calls = [
|
|
339
|
+
const calls = [
|
|
340
|
+
...result.value.calls,
|
|
341
|
+
...indexRenamePartition.kept,
|
|
342
|
+
...schemaDiffPartition.kept,
|
|
343
|
+
...fieldEventPartition.kept,
|
|
344
|
+
];
|
|
311
345
|
const warnings: SqlPlannerConflict[] = [
|
|
312
346
|
...issuePartition.suppressions,
|
|
347
|
+
...indexRenamePartition.suppressions,
|
|
313
348
|
...schemaDiffPartition.suppressions,
|
|
314
349
|
...fieldEventPartition.suppressions,
|
|
315
350
|
].map((record) => renderPostgresSuppression(record, options.contract));
|
|
@@ -323,12 +358,135 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
323
358
|
to: options.contract.storage.storageHash,
|
|
324
359
|
},
|
|
325
360
|
options.spaceId,
|
|
361
|
+
options.snapshotsImportPath,
|
|
326
362
|
this.#lowerer,
|
|
327
363
|
),
|
|
328
364
|
...(warnings.length > 0 ? { warnings: Object.freeze(warnings) } : {}),
|
|
329
365
|
});
|
|
330
366
|
}
|
|
331
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Rename post-pass for indexes, per `(schema, table)`, widening-only,
|
|
370
|
+
* deterministic by sorted names — the same structure as the policy pass
|
|
371
|
+
* below (which stays untouched: policies pair by hash only).
|
|
372
|
+
*
|
|
373
|
+
* Phase 1 — hash pairing: extras whose live names parse as wire names,
|
|
374
|
+
* grouped by `(schema, table, hash)`; missing nodes iterated in sorted-name
|
|
375
|
+
* order consume the sorted-name-first candidate — a prefix-only rename.
|
|
376
|
+
*
|
|
377
|
+
* Phase 2 — content pairing: the remaining managed-missing nodes
|
|
378
|
+
* (`prefix` defined) against the remaining extras of any name shape,
|
|
379
|
+
* paired iff content-equal (columns ordered-strict both-defined-or-
|
|
380
|
+
* both-undefined, `unique`/`type` strict, `options` loose, bodies
|
|
381
|
+
* byte-equal) — exact→managed adoption convergence.
|
|
382
|
+
*
|
|
383
|
+
* Leftovers proceed as create/drop exactly as before; without the
|
|
384
|
+
* widening allowance the pass is skipped and pairing degrades to the
|
|
385
|
+
* additive half, like the policy pass.
|
|
386
|
+
*/
|
|
387
|
+
private pairIndexRenames(
|
|
388
|
+
options: PlannerOptionsWithComponents,
|
|
389
|
+
issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
|
|
390
|
+
): {
|
|
391
|
+
readonly calls: readonly RenameIndexCall[];
|
|
392
|
+
readonly consumed: ReadonlySet<SchemaDiffIssue<SqlSchemaDiffNode>>;
|
|
393
|
+
} {
|
|
394
|
+
const consumed = new Set<SchemaDiffIssue<SqlSchemaDiffNode>>();
|
|
395
|
+
const calls: RenameIndexCall[] = [];
|
|
396
|
+
if (!options.policy.allowedOperationClasses.includes('widening')) {
|
|
397
|
+
return { calls, consumed };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
interface IndexFinding {
|
|
401
|
+
readonly issue: SchemaDiffIssue<SqlSchemaDiffNode>;
|
|
402
|
+
readonly node: SqlIndexIR;
|
|
403
|
+
readonly ddlSchema: string;
|
|
404
|
+
readonly tableName: string;
|
|
405
|
+
}
|
|
406
|
+
const missing: IndexFinding[] = [];
|
|
407
|
+
const extra: IndexFinding[] = [];
|
|
408
|
+
for (const issue of issues) {
|
|
409
|
+
const node = issueNode(issue);
|
|
410
|
+
if (node === undefined || !SqlIndexIR.is(node)) continue;
|
|
411
|
+
const ddlSchema = issue.path[1];
|
|
412
|
+
const tableName = issue.path[2];
|
|
413
|
+
if (ddlSchema === undefined || tableName === undefined) continue;
|
|
414
|
+
if (issueOutcome(issue) === 'not-found') {
|
|
415
|
+
missing.push({ issue, node, ddlSchema, tableName });
|
|
416
|
+
} else if (issueOutcome(issue) === 'not-expected') {
|
|
417
|
+
extra.push({ issue, node, ddlSchema, tableName });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (missing.length === 0 || extra.length === 0) {
|
|
421
|
+
return { calls, consumed };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const byName = (a: IndexFinding, b: IndexFinding): number =>
|
|
425
|
+
a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0;
|
|
426
|
+
// DDL emission must stay unqualified for the unbound namespace, exactly
|
|
427
|
+
// like the per-issue mapping's `emissionSchemaName`.
|
|
428
|
+
const emissionSchema = (ddlSchema: string): string =>
|
|
429
|
+
resolveNamespaceIdForDdlSchema(options.contract, ddlSchema) === UNBOUND_NAMESPACE_ID
|
|
430
|
+
? UNBOUND_NAMESPACE_ID
|
|
431
|
+
: ddlSchema;
|
|
432
|
+
const pairingKey = (finding: IndexFinding, hash: string): string =>
|
|
433
|
+
JSON.stringify([finding.ddlSchema, finding.tableName, hash]);
|
|
434
|
+
const rename = (missingFinding: IndexFinding, candidate: IndexFinding): void => {
|
|
435
|
+
consumed.add(missingFinding.issue);
|
|
436
|
+
consumed.add(candidate.issue);
|
|
437
|
+
calls.push(
|
|
438
|
+
new RenameIndexCall(
|
|
439
|
+
emissionSchema(missingFinding.ddlSchema),
|
|
440
|
+
missingFinding.tableName,
|
|
441
|
+
candidate.node.name,
|
|
442
|
+
missingFinding.node.name,
|
|
443
|
+
),
|
|
444
|
+
);
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const sortedMissing = [...missing].sort(byName);
|
|
448
|
+
|
|
449
|
+
const extrasByHash = new Map<string, IndexFinding[]>();
|
|
450
|
+
for (const finding of extra) {
|
|
451
|
+
const parsed = parseWireName(finding.node.name);
|
|
452
|
+
if (parsed === undefined) continue;
|
|
453
|
+
const key = pairingKey(finding, parsed.hash);
|
|
454
|
+
const group = extrasByHash.get(key) ?? [];
|
|
455
|
+
group.push(finding);
|
|
456
|
+
extrasByHash.set(key, group);
|
|
457
|
+
}
|
|
458
|
+
for (const group of extrasByHash.values()) {
|
|
459
|
+
group.sort(byName);
|
|
460
|
+
}
|
|
461
|
+
for (const missingFinding of sortedMissing) {
|
|
462
|
+
const parsed = parseWireName(missingFinding.node.name);
|
|
463
|
+
if (parsed === undefined) continue;
|
|
464
|
+
const candidate = extrasByHash.get(pairingKey(missingFinding, parsed.hash))?.shift();
|
|
465
|
+
if (candidate === undefined) continue;
|
|
466
|
+
rename(missingFinding, candidate);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const sortedExtras = [...extra].sort(byName);
|
|
470
|
+
for (const missingFinding of sortedMissing) {
|
|
471
|
+
if (consumed.has(missingFinding.issue)) continue;
|
|
472
|
+
if (missingFinding.node.prefix === undefined) continue;
|
|
473
|
+
const candidate = sortedExtras.find(
|
|
474
|
+
(extraFinding) =>
|
|
475
|
+
!consumed.has(extraFinding.issue) &&
|
|
476
|
+
extraFinding.ddlSchema === missingFinding.ddlSchema &&
|
|
477
|
+
extraFinding.tableName === missingFinding.tableName &&
|
|
478
|
+
missingFinding.node.contentEquals(extraFinding.node, {
|
|
479
|
+
columnPresence: 'matching',
|
|
480
|
+
bodies: 'verbatim',
|
|
481
|
+
}),
|
|
482
|
+
);
|
|
483
|
+
if (candidate === undefined) continue;
|
|
484
|
+
rename(missingFinding, candidate);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return { calls, consumed };
|
|
488
|
+
}
|
|
489
|
+
|
|
332
490
|
/**
|
|
333
491
|
* Maps the RLS policy presence findings of the one combined tree diff
|
|
334
492
|
* (`buildPostgresPlanDiff`, already ownership-filtered) into
|
|
@@ -404,7 +562,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
404
562
|
if (allowsWidening) {
|
|
405
563
|
const extrasByGroup = new Map<string, PolicyFinding[]>();
|
|
406
564
|
for (const finding of extra) {
|
|
407
|
-
const parsed =
|
|
565
|
+
const parsed = parseWireName(finding.node.name);
|
|
408
566
|
if (parsed === undefined) continue;
|
|
409
567
|
const key = pairingKey(finding, parsed.hash);
|
|
410
568
|
const group = extrasByGroup.get(key) ?? [];
|
|
@@ -419,7 +577,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
419
577
|
a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0,
|
|
420
578
|
);
|
|
421
579
|
for (const missingFinding of sortedMissing) {
|
|
422
|
-
const parsed =
|
|
580
|
+
const parsed = parseWireName(missingFinding.node.name);
|
|
423
581
|
if (parsed === undefined) continue;
|
|
424
582
|
const candidates = extrasByGroup.get(pairingKey(missingFinding, parsed.hash));
|
|
425
583
|
const candidate = candidates?.shift();
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
DropPostgresRlsPolicyCall,
|
|
35
35
|
DropTableCall,
|
|
36
36
|
EnableRowLevelSecurityCall,
|
|
37
|
+
RenameIndexCall,
|
|
37
38
|
RenamePostgresRlsPolicyCall,
|
|
38
39
|
SetDefaultCall,
|
|
39
40
|
SetNotNullCall,
|
|
@@ -392,22 +393,39 @@ export abstract class PostgresMigration<
|
|
|
392
393
|
);
|
|
393
394
|
}
|
|
394
395
|
|
|
395
|
-
protected createIndex(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
396
|
+
protected createIndex(
|
|
397
|
+
options: {
|
|
398
|
+
readonly schema: string;
|
|
399
|
+
readonly table: string;
|
|
400
|
+
readonly index: string;
|
|
401
|
+
readonly extras?: CreateIndexExtras;
|
|
402
|
+
} & (
|
|
403
|
+
| { readonly columns: readonly string[]; readonly expression?: never }
|
|
404
|
+
| { readonly expression: string; readonly columns?: never }
|
|
405
|
+
),
|
|
406
|
+
): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
|
|
402
407
|
return new CreateIndexCall(
|
|
403
408
|
options.schema,
|
|
404
409
|
options.table,
|
|
405
410
|
options.index,
|
|
406
|
-
options.columns
|
|
411
|
+
options.columns !== undefined
|
|
412
|
+
? { columns: options.columns }
|
|
413
|
+
: { expression: options.expression },
|
|
407
414
|
options.extras,
|
|
408
415
|
).toOp(this.controlAdapterFor('createIndex'));
|
|
409
416
|
}
|
|
410
417
|
|
|
418
|
+
protected renameIndex(options: {
|
|
419
|
+
readonly schema: string;
|
|
420
|
+
readonly table: string;
|
|
421
|
+
readonly from: string;
|
|
422
|
+
readonly to: string;
|
|
423
|
+
}): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {
|
|
424
|
+
return new RenameIndexCall(options.schema, options.table, options.from, options.to).toOp(
|
|
425
|
+
this.controlAdapterFor('renameIndex'),
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
411
429
|
protected dropIndex(options: {
|
|
412
430
|
readonly schema: string;
|
|
413
431
|
readonly table: string;
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
} from '@prisma-next/framework-components/control';
|
|
7
7
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
8
8
|
import { isThenable } from '@prisma-next/utils/promise';
|
|
9
|
+
import { postgresError } from '../errors';
|
|
9
10
|
import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
10
11
|
|
|
11
12
|
type Op = SqlMigrationPlanOperation<PostgresPlanTargetDetails>;
|
|
@@ -23,8 +24,10 @@ function assertPostgresOp(op: MigrationPlanOperation, callFactoryName: string):
|
|
|
23
24
|
'op.target is present on concrete SqlMigrationPlanOperation but absent on the framework MigrationPlanOperation base'
|
|
24
25
|
>(op).target?.id;
|
|
25
26
|
if (targetId !== 'postgres') {
|
|
26
|
-
throw
|
|
27
|
+
throw postgresError(
|
|
28
|
+
'MIGRATION.TARGET_MISMATCH',
|
|
27
29
|
`renderOps: expected postgres op but got target.id="${String(targetId)}" for op.id="${op.id}" (factoryName="${callFactoryName}"). An OpFactoryCall produced an op for a different target on the postgres planner path; check the call's target binding.`,
|
|
30
|
+
{ meta: { opId: op.id, targetId: String(targetId), factoryName: callFactoryName } },
|
|
28
31
|
);
|
|
29
32
|
}
|
|
30
33
|
}
|