@prisma-next/adapter-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/dist/{adapter-BaZsfXGA.mjs → adapter-DlDJIHgB.mjs} +4 -4
- package/dist/adapter-DlDJIHgB.mjs.map +1 -0
- package/dist/adapter.d.mts.map +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/{control-adapter-B6IM_oTR.mjs → control-adapter-DKbwTazN.mjs} +164 -58
- package/dist/control-adapter-DKbwTazN.mjs.map +1 -0
- package/dist/control.d.mts +2 -2
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +269 -34
- package/dist/control.mjs.map +1 -1
- package/dist/{descriptor-meta-jMtx881n.mjs → descriptor-meta-pc7bA0_C.mjs} +26 -6
- package/dist/descriptor-meta-pc7bA0_C.mjs.map +1 -0
- package/dist/runtime.mjs +3 -3
- package/dist/runtime.mjs.map +1 -1
- package/package.json +23 -23
- package/src/core/adapter-errors.ts +25 -0
- package/src/core/adapter.ts +7 -2
- package/src/core/control-adapter.ts +140 -62
- package/src/core/control-codecs.ts +4 -1
- package/src/core/control-mutation-defaults.ts +134 -34
- package/src/core/descriptor-meta.ts +16 -5
- package/src/core/sql-renderer.ts +136 -34
- package/src/exports/control.ts +3 -4
- package/src/exports/runtime.ts +1 -1
- package/dist/adapter-BaZsfXGA.mjs.map +0 -1
- package/dist/control-adapter-B6IM_oTR.mjs.map +0 -1
- package/dist/descriptor-meta-jMtx881n.mjs.map +0 -1
package/src/core/adapter.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import { isDdlNode } from '@prisma-next/sql-relational-core/ast';
|
|
12
12
|
import type { RawCodecInferer } from '@prisma-next/sql-relational-core/expression';
|
|
13
13
|
import type { PostgresDdlNode } from '@prisma-next/target-postgres/ddl';
|
|
14
|
+
import { adapterError } from './adapter-errors';
|
|
14
15
|
import { createPostgresBuiltinCodecLookup } from './codec-lookup';
|
|
15
16
|
import { PostgresControlAdapter } from './control-adapter';
|
|
16
17
|
import { renderLoweredSql } from './sql-renderer';
|
|
@@ -75,8 +76,10 @@ class PostgresAdapterImpl
|
|
|
75
76
|
context: LowererContext<PostgresContract>,
|
|
76
77
|
): PostgresLoweredStatement {
|
|
77
78
|
if (isDdlNode(ast)) {
|
|
78
|
-
throw
|
|
79
|
+
throw adapterError(
|
|
80
|
+
'RUNTIME.DDL_UNSUPPORTED',
|
|
79
81
|
'lower() does not lower DDL on the runtime adapter — DDL lowering is a control-plane concern handled by the control adapter.',
|
|
82
|
+
{ meta: { surface: 'runtime-adapter' } },
|
|
80
83
|
);
|
|
81
84
|
}
|
|
82
85
|
return renderLoweredSql(ast, context.contract, this.codecLookup);
|
|
@@ -98,8 +101,10 @@ export const postgresRawCodecInferer: RawCodecInferer = {
|
|
|
98
101
|
case 'object':
|
|
99
102
|
if (value instanceof Uint8Array) return 'pg/bytea';
|
|
100
103
|
}
|
|
101
|
-
throw
|
|
104
|
+
throw adapterError(
|
|
105
|
+
'RUNTIME.RAW_SQL_UNSUPPORTED_INTERPOLATION',
|
|
102
106
|
'unsupported JS value type for raw-SQL interpolation: wrap this value in `param(...)` with an explicit codec',
|
|
107
|
+
{ meta: { valueType: typeof value } },
|
|
103
108
|
);
|
|
104
109
|
},
|
|
105
110
|
};
|
|
@@ -31,6 +31,7 @@ import type {
|
|
|
31
31
|
SqlExecuteRequest,
|
|
32
32
|
} from '@prisma-next/sql-relational-core/ast';
|
|
33
33
|
import { isDdlNode } from '@prisma-next/sql-relational-core/ast';
|
|
34
|
+
import { parseWireName } from '@prisma-next/sql-schema-ir/naming';
|
|
34
35
|
import type {
|
|
35
36
|
PrimaryKeyInput,
|
|
36
37
|
SqlCheckConstraintIRInput,
|
|
@@ -49,21 +50,24 @@ import type {
|
|
|
49
50
|
AddColumnAction,
|
|
50
51
|
AlterTableActionVisitor,
|
|
51
52
|
DropDefaultAction,
|
|
53
|
+
PostgresAlterIndexRename,
|
|
52
54
|
PostgresAlterPolicyRename,
|
|
53
55
|
PostgresAlterTable,
|
|
56
|
+
PostgresCreateIndex,
|
|
54
57
|
PostgresCreatePolicy,
|
|
55
58
|
PostgresCreateSchema,
|
|
56
59
|
PostgresCreateTable,
|
|
57
60
|
PostgresCreateType,
|
|
58
61
|
PostgresDdlNode,
|
|
59
62
|
PostgresDisableRowLevelSecurity,
|
|
63
|
+
PostgresDropIndex,
|
|
60
64
|
PostgresDropPolicy,
|
|
61
65
|
PostgresDropType,
|
|
62
66
|
RlsPolicyOperation,
|
|
63
67
|
} from '@prisma-next/target-postgres/ddl';
|
|
64
68
|
import { parsePostgresDefault } from '@prisma-next/target-postgres/default-normalizer';
|
|
69
|
+
import { postgresError } from '@prisma-next/target-postgres/errors';
|
|
65
70
|
import { normalizeSchemaNativeType } from '@prisma-next/target-postgres/native-type-normalizer';
|
|
66
|
-
import { parseRlsPolicyWireName } from '@prisma-next/target-postgres/rls-canonicalize';
|
|
67
71
|
import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
|
|
68
72
|
import {
|
|
69
73
|
PostgresDatabaseSchemaNode,
|
|
@@ -76,6 +80,7 @@ import {
|
|
|
76
80
|
} from '@prisma-next/target-postgres/types';
|
|
77
81
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
78
82
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
83
|
+
import { adapterError } from './adapter-errors';
|
|
79
84
|
import { encodeControlQueryParams } from './control-codecs';
|
|
80
85
|
import {
|
|
81
86
|
execute,
|
|
@@ -148,8 +153,10 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
148
153
|
*/
|
|
149
154
|
lower(ast: AnyQueryAst | PostgresDdlNode, context: LowererContext<unknown>): LoweredStatement {
|
|
150
155
|
if (isDdlNode(ast)) {
|
|
151
|
-
throw
|
|
156
|
+
throw adapterError(
|
|
157
|
+
'RUNTIME.DDL_UNSUPPORTED',
|
|
152
158
|
'lower() cannot lower DDL: DDL default literals require inline codec encoding, which is async. Use lowerToExecuteRequest().',
|
|
159
|
+
{ meta: { surface: 'control-adapter' } },
|
|
153
160
|
);
|
|
154
161
|
}
|
|
155
162
|
return renderLoweredSql(
|
|
@@ -867,7 +874,9 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
867
874
|
tablename: string;
|
|
868
875
|
indexname: string;
|
|
869
876
|
indisunique: boolean;
|
|
877
|
+
where_predicate: string | null;
|
|
870
878
|
attname: string | null;
|
|
879
|
+
element_def: string | null;
|
|
871
880
|
index_position: number;
|
|
872
881
|
amname: string | null;
|
|
873
882
|
reloptions: string[] | null;
|
|
@@ -881,11 +890,24 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
881
890
|
// the table order — verification compares against the contract
|
|
882
891
|
// with order-sensitive equality and reports a spurious
|
|
883
892
|
// `index_mismatch`.
|
|
893
|
+
//
|
|
894
|
+
// `element_def` is the per-position element text as Postgres reprints
|
|
895
|
+
// it (`pg_get_indexdef` with pretty-printing); an expression element
|
|
896
|
+
// has attnum 0 so its `attname` is null and the element text is the
|
|
897
|
+
// only faithful capture. A mixed index (columns + expressions) carries
|
|
898
|
+
// the WHOLE element list as one opaque string, so the reprint must
|
|
899
|
+
// fire for every position of an expression-carrying index — the CASE
|
|
900
|
+
// bounds the per-element catalog reconstruction to those indexes,
|
|
901
|
+
// which keeps pure-column schemas free of the reprint cost.
|
|
902
|
+
// `where_predicate` is the reprinted partial-index predicate, null
|
|
903
|
+
// for total indexes.
|
|
884
904
|
`SELECT
|
|
885
905
|
i.tablename,
|
|
886
906
|
i.indexname,
|
|
887
907
|
ix.indisunique,
|
|
908
|
+
pg_get_expr(ix.indpred, ix.indrelid) AS where_predicate,
|
|
888
909
|
a.attname,
|
|
910
|
+
CASE WHEN 0 = ANY(ix.indkey::int[]) THEN pg_get_indexdef(ix.indexrelid, k.ord::int, true) END AS element_def,
|
|
889
911
|
k.ord AS index_position,
|
|
890
912
|
am.amname,
|
|
891
913
|
ic.reloptions
|
|
@@ -1123,38 +1145,24 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1123
1145
|
dependsOn: postgresColumnDependsOn(schema, tableName, uq.columns),
|
|
1124
1146
|
}));
|
|
1125
1147
|
|
|
1126
|
-
// Process indexes
|
|
1148
|
+
// Process indexes — keyed by catalog-unique index name, so two same-
|
|
1149
|
+
// tuple siblings (a unique index beside a redundant plain index) and
|
|
1150
|
+
// expression indexes all enter the tree at full fidelity.
|
|
1127
1151
|
const indexesMap = new Map<
|
|
1128
1152
|
string,
|
|
1129
1153
|
{
|
|
1130
|
-
columns: string[];
|
|
1131
1154
|
name: string;
|
|
1155
|
+
elements: { attname: string | null; elementDef: string | null }[];
|
|
1132
1156
|
unique: boolean;
|
|
1157
|
+
where: string | null;
|
|
1133
1158
|
type: string | undefined;
|
|
1134
1159
|
options: Record<string, string> | undefined;
|
|
1135
1160
|
}
|
|
1136
1161
|
>();
|
|
1137
|
-
// An index with an expression key (e.g. `lower(email)`) reports that
|
|
1138
|
-
// key's row with `attname = null` (Postgres attribute numbers are
|
|
1139
|
-
// <= 0 for expressions, which the LEFT JOIN above can't resolve to a
|
|
1140
|
-
// real column). Every row for that index name is skipped below
|
|
1141
|
-
// rather than only the expression row, so the index never enters
|
|
1142
|
-
// `indexesMap` with a collapsed, misleading column list — a
|
|
1143
|
-
// two-column expression index silently reduced to its one real
|
|
1144
|
-
// column can coincide with an unrelated real single-column index,
|
|
1145
|
-
// and the schema differ's diff-tree node id is derived from the
|
|
1146
|
-
// column tuple (`sql-index/index:<columns>`), so two indexes
|
|
1147
|
-
// colliding on that tuple abort the diff with "duplicate id among
|
|
1148
|
-
// siblings" instead of a normal drift report.
|
|
1149
|
-
const indexNamesWithExpressionKey = new Set<string>();
|
|
1150
1162
|
for (const idxRow of indexesByTable.get(tableName) ?? []) {
|
|
1151
|
-
if (!idxRow.attname) {
|
|
1152
|
-
indexNamesWithExpressionKey.add(idxRow.indexname);
|
|
1153
|
-
continue;
|
|
1154
|
-
}
|
|
1155
1163
|
const existing = indexesMap.get(idxRow.indexname);
|
|
1156
1164
|
if (existing) {
|
|
1157
|
-
existing.
|
|
1165
|
+
existing.elements.push({ attname: idxRow.attname, elementDef: idxRow.element_def });
|
|
1158
1166
|
} else {
|
|
1159
1167
|
// Drop btree (the Postgres default) so a contract index without an
|
|
1160
1168
|
// explicit type matches a default-method introspected index without
|
|
@@ -1162,49 +1170,46 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1162
1170
|
const indexType = idxRow.amname && idxRow.amname !== 'btree' ? idxRow.amname : undefined;
|
|
1163
1171
|
const indexOptions = parsePgReloptions(idxRow.reloptions, idxRow.indexname);
|
|
1164
1172
|
indexesMap.set(idxRow.indexname, {
|
|
1165
|
-
columns: [idxRow.attname],
|
|
1166
1173
|
name: idxRow.indexname,
|
|
1174
|
+
elements: [{ attname: idxRow.attname, elementDef: idxRow.element_def }],
|
|
1167
1175
|
unique: idxRow.indisunique,
|
|
1176
|
+
where: idxRow.where_predicate,
|
|
1168
1177
|
type: indexType,
|
|
1169
1178
|
options: indexOptions,
|
|
1170
1179
|
});
|
|
1171
1180
|
}
|
|
1172
1181
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
// index is a strict superset of what a plain index on the same
|
|
1182
|
-
// columns would add), otherwise the first by name for determinism.
|
|
1183
|
-
const survivingIndexes = Array.from(indexesMap.values()).filter(
|
|
1184
|
-
(idx) => !indexNamesWithExpressionKey.has(idx.name),
|
|
1185
|
-
);
|
|
1186
|
-
const bestByColumnTuple = new Map<string, (typeof survivingIndexes)[number]>();
|
|
1187
|
-
for (const idx of survivingIndexes) {
|
|
1188
|
-
const tupleKey = idx.columns.join(',');
|
|
1189
|
-
const existing = bestByColumnTuple.get(tupleKey);
|
|
1190
|
-
if (
|
|
1191
|
-
!existing ||
|
|
1192
|
-
(idx.unique && !existing.unique) ||
|
|
1193
|
-
(idx.unique === existing.unique && idx.name < existing.name)
|
|
1194
|
-
) {
|
|
1195
|
-
bestByColumnTuple.set(tupleKey, idx);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
const indexes: readonly SqlIndexIRInput[] = Array.from(bestByColumnTuple.values()).map(
|
|
1199
|
-
(idx) => ({
|
|
1200
|
-
columns: Object.freeze([...idx.columns]),
|
|
1182
|
+
const indexes: readonly SqlIndexIRInput[] = Array.from(indexesMap.values()).map((idx) => {
|
|
1183
|
+
// An expression element has attnum 0, which the attribute LEFT JOIN
|
|
1184
|
+
// cannot resolve — its attname is null. Any such element makes the
|
|
1185
|
+
// whole index an expression node: the entire element list (real
|
|
1186
|
+
// columns included) is carried as one opaque reprinted string.
|
|
1187
|
+
const isExpression = idx.elements.some((el) => el.attname === null);
|
|
1188
|
+
const columnNames = idx.elements.flatMap((el) => (el.attname !== null ? [el.attname] : []));
|
|
1189
|
+
const base = {
|
|
1201
1190
|
name: idx.name,
|
|
1191
|
+
// Rename-pass grouping only, like policy introspection: undefined
|
|
1192
|
+
// when the live name does not follow the wire-name shape.
|
|
1193
|
+
prefix: parseWireName(idx.name)?.prefix,
|
|
1194
|
+
where: idx.where ?? undefined,
|
|
1202
1195
|
unique: idx.unique,
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1196
|
+
partial: idx.where !== null,
|
|
1197
|
+
type: idx.type,
|
|
1198
|
+
options: idx.options,
|
|
1199
|
+
annotations: undefined,
|
|
1200
|
+
// Expression indexes stamp chains to every column of the table —
|
|
1201
|
+
// the opaque expression is never parsed, so the deterministic
|
|
1202
|
+
// over-approximation keeps drops ordered.
|
|
1203
|
+
dependsOn: postgresColumnDependsOn(
|
|
1204
|
+
schema,
|
|
1205
|
+
tableName,
|
|
1206
|
+
isExpression ? Object.keys(columns) : columnNames,
|
|
1207
|
+
),
|
|
1208
|
+
};
|
|
1209
|
+
return isExpression
|
|
1210
|
+
? { ...base, expression: idx.elements.map((el) => el.elementDef ?? '').join(', ') }
|
|
1211
|
+
: { ...base, columns: Object.freeze([...columnNames]) };
|
|
1212
|
+
});
|
|
1208
1213
|
|
|
1209
1214
|
// Process check constraints — parse each predicate into column + value set.
|
|
1210
1215
|
// Only the two shapes emitted by this slice are recognised; free-form
|
|
@@ -1274,7 +1279,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1274
1279
|
...new Set(parsePgNameArray(row.roles).map((r) => r.toLowerCase())),
|
|
1275
1280
|
].sort();
|
|
1276
1281
|
const permissive = row.permissive.toUpperCase() === 'PERMISSIVE';
|
|
1277
|
-
const prefix =
|
|
1282
|
+
const prefix = parseWireName(row.policyname)?.prefix ?? row.policyname;
|
|
1278
1283
|
const policy = new PostgresPolicySchemaNode({
|
|
1279
1284
|
name: row.policyname,
|
|
1280
1285
|
prefix,
|
|
@@ -1542,8 +1547,10 @@ const PG_REFERENTIAL_ACTION_MAP: Record<PgReferentialActionRule, SqlReferentialA
|
|
|
1542
1547
|
function mapReferentialAction(rule: string): SqlReferentialAction | undefined {
|
|
1543
1548
|
const mapped = PG_REFERENTIAL_ACTION_MAP[rule as PgReferentialActionRule];
|
|
1544
1549
|
if (mapped === undefined) {
|
|
1545
|
-
throw
|
|
1550
|
+
throw adapterError(
|
|
1551
|
+
'CONTRACT.INTROSPECTION_UNSUPPORTED',
|
|
1546
1552
|
`Unknown PostgreSQL referential action rule: "${rule}". Expected one of: NO ACTION, RESTRICT, CASCADE, SET NULL, SET DEFAULT.`,
|
|
1553
|
+
{ meta: { rule } },
|
|
1547
1554
|
);
|
|
1548
1555
|
}
|
|
1549
1556
|
if (mapped === 'noAction') return undefined;
|
|
@@ -1614,8 +1621,10 @@ export function parsePgReloptions(
|
|
|
1614
1621
|
for (const entry of reloptions) {
|
|
1615
1622
|
const eq = entry.indexOf('=');
|
|
1616
1623
|
if (eq === -1) {
|
|
1617
|
-
throw
|
|
1624
|
+
throw adapterError(
|
|
1625
|
+
'CONTRACT.INTROSPECTION_UNSUPPORTED',
|
|
1618
1626
|
`Postgres introspection: malformed reloption entry "${entry}" on index "${indexName}" (expected "key=value")`,
|
|
1627
|
+
{ meta: { entry, indexName } },
|
|
1619
1628
|
);
|
|
1620
1629
|
}
|
|
1621
1630
|
const key = entry.slice(0, eq);
|
|
@@ -1771,8 +1780,10 @@ function pgInlineLiteral(wire: unknown, nativeType: string): string {
|
|
|
1771
1780
|
if (typeof wire === 'boolean') return wire ? 'true' : 'false';
|
|
1772
1781
|
if (typeof wire === 'number') {
|
|
1773
1782
|
if (!Number.isFinite(wire)) {
|
|
1774
|
-
throw
|
|
1783
|
+
throw adapterError(
|
|
1784
|
+
'CONTRACT.DEFAULT_INVALID',
|
|
1775
1785
|
`pgRenderDdlExecuteRequest: non-finite number wire value ${String(wire)} cannot be emitted as a DEFAULT literal for native type "${nativeType}"`,
|
|
1786
|
+
{ meta: { nativeType } },
|
|
1776
1787
|
);
|
|
1777
1788
|
}
|
|
1778
1789
|
return String(wire);
|
|
@@ -1780,8 +1791,10 @@ function pgInlineLiteral(wire: unknown, nativeType: string): string {
|
|
|
1780
1791
|
if (typeof wire === 'bigint') return String(wire);
|
|
1781
1792
|
if (wire instanceof Date) {
|
|
1782
1793
|
if (Number.isNaN(wire.getTime())) {
|
|
1783
|
-
throw
|
|
1794
|
+
throw adapterError(
|
|
1795
|
+
'CONTRACT.DEFAULT_INVALID',
|
|
1784
1796
|
`pgRenderDdlExecuteRequest: invalid Date value cannot be emitted as a DEFAULT literal for native type "${nativeType}"`,
|
|
1797
|
+
{ meta: { nativeType } },
|
|
1785
1798
|
);
|
|
1786
1799
|
}
|
|
1787
1800
|
const quoted = `'${escapeLiteral(wire.toISOString())}'`;
|
|
@@ -1804,8 +1817,10 @@ function pgInlineLiteral(wire: unknown, nativeType: string): string {
|
|
|
1804
1817
|
const quoted = `'${escapeLiteral(JSON.stringify(wire))}'`;
|
|
1805
1818
|
return `${quoted}::${nativeType}`;
|
|
1806
1819
|
}
|
|
1807
|
-
throw
|
|
1820
|
+
throw adapterError(
|
|
1821
|
+
'CONTRACT.PACK_CONTRIBUTION_INVALID',
|
|
1808
1822
|
`pgRenderDdlExecuteRequest: unexpected wire type "${typeof wire}" for native type "${nativeType}"`,
|
|
1823
|
+
{ meta: { wireType: typeof wire, nativeType } },
|
|
1809
1824
|
);
|
|
1810
1825
|
}
|
|
1811
1826
|
|
|
@@ -1991,6 +2006,65 @@ function pgRenderAlterPolicyRename(node: PostgresAlterPolicyRename): SqlExecuteR
|
|
|
1991
2006
|
};
|
|
1992
2007
|
}
|
|
1993
2008
|
|
|
2009
|
+
/**
|
|
2010
|
+
* Renders one index reloption value: strings single-quote-escaped, finite
|
|
2011
|
+
* numbers verbatim, booleans in the `on`/`off` catalog spelling (the
|
|
2012
|
+
* canonical form the wire hash and the option equality commit to).
|
|
2013
|
+
*/
|
|
2014
|
+
function pgRenderIndexOptionValue(key: string, value: unknown): string {
|
|
2015
|
+
if (typeof value === 'string') return `'${escapeLiteral(value)}'`;
|
|
2016
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
2017
|
+
if (typeof value === 'boolean') return value ? 'on' : 'off';
|
|
2018
|
+
throw postgresError(
|
|
2019
|
+
'CONTRACT.INDEX_INVALID',
|
|
2020
|
+
`Index option "${key}" must be a string, finite number, or boolean; got ${typeof value}`,
|
|
2021
|
+
{ meta: { key, valueType: typeof value } },
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
/** Qualifies an object name; an absent schema renders it unqualified (unbound namespace). */
|
|
2026
|
+
function pgQualify(schema: string | undefined, name: string): string {
|
|
2027
|
+
return schema === undefined
|
|
2028
|
+
? quoteIdentifier(name)
|
|
2029
|
+
: `${quoteIdentifier(schema)}.${quoteIdentifier(name)}`;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
function pgRenderCreateIndex(node: PostgresCreateIndex): SqlExecuteRequest {
|
|
2033
|
+
const elementList =
|
|
2034
|
+
'columns' in node.elements
|
|
2035
|
+
? node.elements.columns.map(quoteIdentifier).join(', ')
|
|
2036
|
+
: node.elements.expression;
|
|
2037
|
+
const unique = node.unique ? 'UNIQUE ' : '';
|
|
2038
|
+
const using = node.type !== undefined ? ` USING ${quoteIdentifier(node.type)}` : '';
|
|
2039
|
+
const withClause =
|
|
2040
|
+
node.options !== undefined && Object.keys(node.options).length > 0
|
|
2041
|
+
? ` WITH (${Object.entries(node.options)
|
|
2042
|
+
.map(
|
|
2043
|
+
([key, value]) => `${quoteIdentifier(key)} = ${pgRenderIndexOptionValue(key, value)}`,
|
|
2044
|
+
)
|
|
2045
|
+
.join(', ')})`
|
|
2046
|
+
: '';
|
|
2047
|
+
const whereClause = node.where !== undefined ? ` WHERE (${node.where})` : '';
|
|
2048
|
+
return {
|
|
2049
|
+
sql: `CREATE ${unique}INDEX ${quoteIdentifier(node.name)} ON ${pgQualify(node.schema, node.table)}${using} (${elementList})${withClause}${whereClause}`,
|
|
2050
|
+
params: [],
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
function pgRenderDropIndex(node: PostgresDropIndex): SqlExecuteRequest {
|
|
2055
|
+
return {
|
|
2056
|
+
sql: `DROP INDEX ${pgQualify(node.schema, node.name)}`,
|
|
2057
|
+
params: [],
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
function pgRenderAlterIndexRename(node: PostgresAlterIndexRename): SqlExecuteRequest {
|
|
2062
|
+
return {
|
|
2063
|
+
sql: `ALTER INDEX ${pgQualify(node.schema, node.from)} RENAME TO ${quoteIdentifier(node.to)}`,
|
|
2064
|
+
params: [],
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
|
|
1994
2068
|
function pgRenderDisableRowLevelSecurity(node: PostgresDisableRowLevelSecurity): SqlExecuteRequest {
|
|
1995
2069
|
const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
|
|
1996
2070
|
return {
|
|
@@ -2013,6 +2087,10 @@ async function pgRenderDdlExecuteRequest(
|
|
|
2013
2087
|
dropPolicy: (node: PostgresDropPolicy) => Promise.resolve(pgRenderDropPolicy(node)),
|
|
2014
2088
|
alterPolicyRename: (node: PostgresAlterPolicyRename) =>
|
|
2015
2089
|
Promise.resolve(pgRenderAlterPolicyRename(node)),
|
|
2090
|
+
createIndex: (node: PostgresCreateIndex) => Promise.resolve(pgRenderCreateIndex(node)),
|
|
2091
|
+
dropIndex: (node: PostgresDropIndex) => Promise.resolve(pgRenderDropIndex(node)),
|
|
2092
|
+
alterIndexRename: (node: PostgresAlterIndexRename) =>
|
|
2093
|
+
Promise.resolve(pgRenderAlterIndexRename(node)),
|
|
2016
2094
|
disableRowLevelSecurity: (node: PostgresDisableRowLevelSecurity) =>
|
|
2017
2095
|
Promise.resolve(pgRenderDisableRowLevelSecurity(node)),
|
|
2018
2096
|
};
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
encodeParamsWithMetadata,
|
|
10
10
|
} from '@prisma-next/sql-runtime';
|
|
11
11
|
import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
|
|
12
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
12
13
|
|
|
13
14
|
export const CONTROL_CODECS = createAstCodecRegistry(postgresCodecRegistry);
|
|
14
15
|
|
|
@@ -19,7 +20,9 @@ export async function encodeControlQueryParams(
|
|
|
19
20
|
): Promise<readonly unknown[]> {
|
|
20
21
|
const values = lowered.params.map((slot) => {
|
|
21
22
|
if (slot.kind === 'literal') return slot.value;
|
|
22
|
-
throw new
|
|
23
|
+
throw new InternalError(
|
|
24
|
+
`control query lowered to a bind slot '${slot.name}', which is unsupported`,
|
|
25
|
+
);
|
|
23
26
|
});
|
|
24
27
|
return encodeParamsWithMetadata(values, deriveParamMetadata(ast), {}, codecs);
|
|
25
28
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExecutionMutationDefaultValue } from '@prisma-next/contract/types';
|
|
2
2
|
import { timestampNowControlDescriptor } from '@prisma-next/family-sql/control';
|
|
3
|
+
import type { AuthoringTypeNamespace } from '@prisma-next/framework-components/authoring';
|
|
3
4
|
import type {
|
|
4
5
|
ControlMutationDefaultEntry,
|
|
5
6
|
DefaultFunctionLoweringContext,
|
|
@@ -7,10 +8,7 @@ import type {
|
|
|
7
8
|
MutationDefaultGeneratorDescriptor,
|
|
8
9
|
TypedDefaultFunctionCall,
|
|
9
10
|
} from '@prisma-next/framework-components/control';
|
|
10
|
-
import {
|
|
11
|
-
builtinGeneratorRegistryMetadata,
|
|
12
|
-
resolveBuiltinGeneratedColumnDescriptor,
|
|
13
|
-
} from '@prisma-next/ids';
|
|
11
|
+
import { builtinGeneratorRegistryMetadata } from '@prisma-next/ids';
|
|
14
12
|
import type { FuncCallSig } from '@prisma-next/psl-parser';
|
|
15
13
|
import { int, num, oneOf, optional, str } from '@prisma-next/psl-parser';
|
|
16
14
|
|
|
@@ -153,17 +151,138 @@ const postgresDefaultFunctionRegistryEntries = [
|
|
|
153
151
|
],
|
|
154
152
|
] satisfies ReadonlyArray<readonly [string, ControlMutationDefaultEntry]>;
|
|
155
153
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
154
|
+
/**
|
|
155
|
+
* The base PSL scalars as zero-arg type constructors in the unified authoring
|
|
156
|
+
* channel, with explicit `nativeType` values pinned to the codec manifests
|
|
157
|
+
* (`codecLookup.targetTypesFor(codecId)[0]`).
|
|
158
|
+
*
|
|
159
|
+
* The type position is the only storage decider: a mutation-default generator
|
|
160
|
+
* (`@default(uuid())`) never re-picks a column's storage.
|
|
161
|
+
*/
|
|
162
|
+
export const postgresScalarAuthoringTypes = {
|
|
163
|
+
String: {
|
|
164
|
+
kind: 'typeConstructor',
|
|
165
|
+
output: { codecId: 'pg/text@1', nativeType: 'text' },
|
|
166
|
+
},
|
|
167
|
+
Boolean: {
|
|
168
|
+
kind: 'typeConstructor',
|
|
169
|
+
output: { codecId: 'pg/bool@1', nativeType: 'bool' },
|
|
170
|
+
},
|
|
171
|
+
Int: {
|
|
172
|
+
kind: 'typeConstructor',
|
|
173
|
+
output: { codecId: 'pg/int4@1', nativeType: 'int4' },
|
|
174
|
+
},
|
|
175
|
+
BigInt: {
|
|
176
|
+
kind: 'typeConstructor',
|
|
177
|
+
output: { codecId: 'pg/int8@1', nativeType: 'int8' },
|
|
178
|
+
},
|
|
179
|
+
Float: {
|
|
180
|
+
kind: 'typeConstructor',
|
|
181
|
+
output: { codecId: 'pg/float8@1', nativeType: 'float8' },
|
|
182
|
+
},
|
|
183
|
+
Decimal: {
|
|
184
|
+
kind: 'typeConstructor',
|
|
185
|
+
output: { codecId: 'pg/numeric@1', nativeType: 'numeric' },
|
|
186
|
+
},
|
|
187
|
+
DateTime: {
|
|
188
|
+
kind: 'typeConstructor',
|
|
189
|
+
output: { codecId: 'pg/timestamptz@1', nativeType: 'timestamptz' },
|
|
190
|
+
},
|
|
191
|
+
Json: {
|
|
192
|
+
kind: 'typeConstructor',
|
|
193
|
+
output: { codecId: 'pg/json@1', nativeType: 'json' },
|
|
194
|
+
},
|
|
195
|
+
Jsonb: {
|
|
196
|
+
kind: 'typeConstructor',
|
|
197
|
+
output: { codecId: 'pg/jsonb@1', nativeType: 'jsonb' },
|
|
198
|
+
},
|
|
199
|
+
Bytes: {
|
|
200
|
+
kind: 'typeConstructor',
|
|
201
|
+
output: { codecId: 'pg/bytea@1', nativeType: 'bytea' },
|
|
202
|
+
},
|
|
203
|
+
} as const satisfies AuthoringTypeNamespace;
|
|
204
|
+
|
|
205
|
+
export const postgresNativeAuthoringTypes = {
|
|
206
|
+
VarChar: {
|
|
207
|
+
kind: 'typeConstructor',
|
|
208
|
+
args: [{ kind: 'number', name: 'length', integer: true, minimum: 1, optional: true }],
|
|
209
|
+
output: {
|
|
210
|
+
codecId: 'sql/varchar@1',
|
|
211
|
+
nativeType: 'character varying',
|
|
212
|
+
typeParams: { length: { kind: 'arg', index: 0 } },
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
Char: {
|
|
216
|
+
kind: 'typeConstructor',
|
|
217
|
+
args: [{ kind: 'number', name: 'length', integer: true, minimum: 1, optional: true }],
|
|
218
|
+
output: {
|
|
219
|
+
codecId: 'sql/char@1',
|
|
220
|
+
nativeType: 'character',
|
|
221
|
+
typeParams: { length: { kind: 'arg', index: 0 } },
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
Numeric: {
|
|
225
|
+
kind: 'typeConstructor',
|
|
226
|
+
args: [
|
|
227
|
+
{ kind: 'number', name: 'precision', integer: true, minimum: 1, optional: true },
|
|
228
|
+
{ kind: 'number', name: 'scale', integer: true, minimum: 0, optional: true },
|
|
229
|
+
],
|
|
230
|
+
output: {
|
|
231
|
+
codecId: 'pg/numeric@1',
|
|
232
|
+
nativeType: 'numeric',
|
|
233
|
+
typeParams: {
|
|
234
|
+
precision: { kind: 'arg', index: 0 },
|
|
235
|
+
scale: { kind: 'arg', index: 1 },
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
Timestamp: {
|
|
240
|
+
kind: 'typeConstructor',
|
|
241
|
+
args: [{ kind: 'number', name: 'precision', integer: true, minimum: 0, optional: true }],
|
|
242
|
+
output: {
|
|
243
|
+
codecId: 'pg/timestamp@1',
|
|
244
|
+
nativeType: 'timestamp',
|
|
245
|
+
typeParams: { precision: { kind: 'arg', index: 0 } },
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
Timestamptz: {
|
|
249
|
+
kind: 'typeConstructor',
|
|
250
|
+
args: [{ kind: 'number', name: 'precision', integer: true, minimum: 0, optional: true }],
|
|
251
|
+
output: {
|
|
252
|
+
codecId: 'pg/timestamptz@1',
|
|
253
|
+
nativeType: 'timestamptz',
|
|
254
|
+
typeParams: { precision: { kind: 'arg', index: 0 } },
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
Time: {
|
|
258
|
+
kind: 'typeConstructor',
|
|
259
|
+
args: [{ kind: 'number', name: 'precision', integer: true, minimum: 0, optional: true }],
|
|
260
|
+
output: {
|
|
261
|
+
codecId: 'pg/time@1',
|
|
262
|
+
nativeType: 'time',
|
|
263
|
+
typeParams: { precision: { kind: 'arg', index: 0 } },
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
Timetz: {
|
|
267
|
+
kind: 'typeConstructor',
|
|
268
|
+
args: [{ kind: 'number', name: 'precision', integer: true, minimum: 0, optional: true }],
|
|
269
|
+
output: {
|
|
270
|
+
codecId: 'pg/timetz@1',
|
|
271
|
+
nativeType: 'timetz',
|
|
272
|
+
typeParams: { precision: { kind: 'arg', index: 0 } },
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
Uuid: { kind: 'typeConstructor', output: { codecId: 'pg/uuid@1', nativeType: 'uuid' } },
|
|
276
|
+
Inet: { kind: 'typeConstructor', output: { codecId: 'pg/inet@1', nativeType: 'inet' } },
|
|
277
|
+
SmallInt: { kind: 'typeConstructor', output: { codecId: 'pg/int2@1', nativeType: 'int2' } },
|
|
278
|
+
Real: { kind: 'typeConstructor', output: { codecId: 'pg/float4@1', nativeType: 'float4' } },
|
|
279
|
+
Date: { kind: 'typeConstructor', output: { codecId: 'pg/date@1', nativeType: 'date' } },
|
|
280
|
+
} as const satisfies AuthoringTypeNamespace;
|
|
281
|
+
|
|
282
|
+
export const postgresAuthoringTypes = {
|
|
283
|
+
...postgresScalarAuthoringTypes,
|
|
284
|
+
...postgresNativeAuthoringTypes,
|
|
285
|
+
} as const satisfies AuthoringTypeNamespace;
|
|
167
286
|
|
|
168
287
|
export function createPostgresDefaultFunctionRegistry(): ReadonlyMap<
|
|
169
288
|
string,
|
|
@@ -178,27 +297,8 @@ export function createPostgresMutationDefaultGeneratorDescriptors(): readonly Mu
|
|
|
178
297
|
({ id, applicableCodecIds }): MutationDefaultGeneratorDescriptor => ({
|
|
179
298
|
id,
|
|
180
299
|
applicableCodecIds,
|
|
181
|
-
resolveGeneratedColumnDescriptor: ({ generated }) => {
|
|
182
|
-
if (generated.kind !== 'generator' || generated.id !== id) {
|
|
183
|
-
return undefined;
|
|
184
|
-
}
|
|
185
|
-
const descriptor = resolveBuiltinGeneratedColumnDescriptor({
|
|
186
|
-
id,
|
|
187
|
-
...(generated.params ? { params: generated.params } : {}),
|
|
188
|
-
});
|
|
189
|
-
return {
|
|
190
|
-
codecId: descriptor.type.codecId,
|
|
191
|
-
nativeType: descriptor.type.nativeType,
|
|
192
|
-
...(descriptor.type.typeRef ? { typeRef: descriptor.type.typeRef } : {}),
|
|
193
|
-
...(descriptor.typeParams ? { typeParams: descriptor.typeParams } : {}),
|
|
194
|
-
};
|
|
195
|
-
},
|
|
196
300
|
}),
|
|
197
301
|
),
|
|
198
302
|
timestampNowControlDescriptor(),
|
|
199
303
|
];
|
|
200
304
|
}
|
|
201
|
-
|
|
202
|
-
export function createPostgresScalarTypeDescriptors(): ReadonlyMap<string, string> {
|
|
203
|
-
return new Map(postgresScalarTypeDescriptors);
|
|
204
|
-
}
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from '@prisma-next/target-postgres/codec-ids';
|
|
41
41
|
import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
|
|
42
42
|
import type { QueryOperationTypes } from '../types/operation-types';
|
|
43
|
+
import { adapterError } from './adapter-errors';
|
|
43
44
|
|
|
44
45
|
// ============================================================================ Helper functions for reducing boilerplate ============================================================================
|
|
45
46
|
|
|
@@ -69,8 +70,10 @@ function expandLength({ nativeType, typeParams }: ExpandNativeTypeInput): string
|
|
|
69
70
|
}
|
|
70
71
|
const length = typeParams['length'];
|
|
71
72
|
if (!isPositiveInteger(length)) {
|
|
72
|
-
throw
|
|
73
|
+
throw adapterError(
|
|
74
|
+
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
73
75
|
`Invalid "length" type parameter for "${nativeType}": expected a positive integer, got ${JSON.stringify(length)}`,
|
|
76
|
+
{ meta: { nativeType, param: 'length', received: length } },
|
|
74
77
|
);
|
|
75
78
|
}
|
|
76
79
|
return `${nativeType}(${length})`;
|
|
@@ -82,8 +85,10 @@ function expandPrecision({ nativeType, typeParams }: ExpandNativeTypeInput): str
|
|
|
82
85
|
}
|
|
83
86
|
const precision = typeParams['precision'];
|
|
84
87
|
if (!isPositiveInteger(precision)) {
|
|
85
|
-
throw
|
|
88
|
+
throw adapterError(
|
|
89
|
+
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
86
90
|
`Invalid "precision" type parameter for "${nativeType}": expected a positive integer, got ${JSON.stringify(precision)}`,
|
|
91
|
+
{ meta: { nativeType, param: 'precision', received: precision } },
|
|
87
92
|
);
|
|
88
93
|
}
|
|
89
94
|
return `${nativeType}(${precision})`;
|
|
@@ -98,23 +103,29 @@ function expandNumeric({ nativeType, typeParams }: ExpandNativeTypeInput): strin
|
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
if (!hasPrecision && hasScale) {
|
|
101
|
-
throw
|
|
106
|
+
throw adapterError(
|
|
107
|
+
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
102
108
|
`Invalid type parameters for "${nativeType}": "scale" requires "precision" to be specified`,
|
|
109
|
+
{ meta: { nativeType, param: 'scale' } },
|
|
103
110
|
);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
if (hasPrecision) {
|
|
107
114
|
const precision = typeParams['precision'];
|
|
108
115
|
if (!isPositiveInteger(precision)) {
|
|
109
|
-
throw
|
|
116
|
+
throw adapterError(
|
|
117
|
+
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
110
118
|
`Invalid "precision" type parameter for "${nativeType}": expected a positive integer, got ${JSON.stringify(precision)}`,
|
|
119
|
+
{ meta: { nativeType, param: 'precision', received: precision } },
|
|
111
120
|
);
|
|
112
121
|
}
|
|
113
122
|
if (hasScale) {
|
|
114
123
|
const scale = typeParams['scale'];
|
|
115
124
|
if (!isNonNegativeInteger(scale)) {
|
|
116
|
-
throw
|
|
125
|
+
throw adapterError(
|
|
126
|
+
'RUNTIME.TYPE_PARAMS_INVALID',
|
|
117
127
|
`Invalid "scale" type parameter for "${nativeType}": expected a non-negative integer, got ${JSON.stringify(scale)}`,
|
|
128
|
+
{ meta: { nativeType, param: 'scale', received: scale } },
|
|
118
129
|
);
|
|
119
130
|
}
|
|
120
131
|
return `${nativeType}(${precision},${scale})`;
|