@prisma-next/adapter-postgres 0.16.0-dev.3 → 0.16.0-dev.30

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.
@@ -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 new Error(
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 new Error(
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 new Error(
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.columns.push(idxRow.attname);
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
- // Two real indexes can legitimately share the exact same column tuple
1174
- // on one table (e.g. a unique index and a redundant plain index) —
1175
- // valid in Postgres, but the schema differ's diff-tree node id for an
1176
- // index is the column tuple alone (`SqlIndexIR#id`, deliberately —
1177
- // see its doc comment), so two same-tuple siblings from one
1178
- // introspection abort the diff with "duplicate id among siblings"
1179
- // rather than a normal drift report. Keep only one per column tuple:
1180
- // the unique one when there is a unique/non-unique pair (a unique
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
- ...(idx.type !== undefined && { type: idx.type }),
1204
- ...(idx.options !== undefined && { options: idx.options }),
1205
- dependsOn: postgresColumnDependsOn(schema, tableName, idx.columns),
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 = parseRlsPolicyWireName(row.policyname)?.prefix ?? row.policyname;
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 new Error(
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 new Error(
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 new Error(
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 new Error(
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 new Error(
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 Error(`control query lowered to a bind slot '${slot.name}', which is unsupported`);
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
- const postgresScalarTypeDescriptors = new Map<string, string>([
157
- ['String', 'pg/text@1'],
158
- ['Boolean', 'pg/bool@1'],
159
- ['Int', 'pg/int4@1'],
160
- ['BigInt', 'pg/int8@1'],
161
- ['Float', 'pg/float8@1'],
162
- ['Decimal', 'pg/numeric@1'],
163
- ['DateTime', 'pg/timestamptz@1'],
164
- ['Json', 'pg/jsonb@1'],
165
- ['Bytes', 'pg/bytea@1'],
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 new Error(
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 new Error(
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 new Error(
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 new Error(
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 new Error(
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})`;