@prisma-next/adapter-postgres 0.16.0-dev.2 → 0.16.0-dev.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,144 @@ 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
+ /**
206
+ * The former `@db.*` native types as first-class top-level type constructors
207
+ * (TML-2986). Codec ids, native types, and typeParams key shapes mirror the
208
+ * legacy `@db.*` attribute path (`NATIVE_TYPE_SPECS` in sql-contract-psl)
209
+ * exactly; every argument is optional, so each name is also authorable bare
210
+ * (`VarChar` ≡ `VarChar()`), and omitted arguments omit their typeParams keys.
211
+ */
212
+ export const postgresNativeAuthoringTypes = {
213
+ VarChar: {
214
+ kind: 'typeConstructor',
215
+ args: [{ kind: 'number', name: 'length', integer: true, minimum: 1, optional: true }],
216
+ output: {
217
+ codecId: 'sql/varchar@1',
218
+ nativeType: 'character varying',
219
+ typeParams: { length: { kind: 'arg', index: 0 } },
220
+ },
221
+ },
222
+ Char: {
223
+ kind: 'typeConstructor',
224
+ args: [{ kind: 'number', name: 'length', integer: true, minimum: 1, optional: true }],
225
+ output: {
226
+ codecId: 'sql/char@1',
227
+ nativeType: 'character',
228
+ typeParams: { length: { kind: 'arg', index: 0 } },
229
+ },
230
+ },
231
+ Numeric: {
232
+ kind: 'typeConstructor',
233
+ args: [
234
+ { kind: 'number', name: 'precision', integer: true, minimum: 1, optional: true },
235
+ { kind: 'number', name: 'scale', integer: true, minimum: 0, optional: true },
236
+ ],
237
+ output: {
238
+ codecId: 'pg/numeric@1',
239
+ nativeType: 'numeric',
240
+ typeParams: {
241
+ precision: { kind: 'arg', index: 0 },
242
+ scale: { kind: 'arg', index: 1 },
243
+ },
244
+ },
245
+ },
246
+ Timestamp: {
247
+ kind: 'typeConstructor',
248
+ args: [{ kind: 'number', name: 'precision', integer: true, minimum: 0, optional: true }],
249
+ output: {
250
+ codecId: 'pg/timestamp@1',
251
+ nativeType: 'timestamp',
252
+ typeParams: { precision: { kind: 'arg', index: 0 } },
253
+ },
254
+ },
255
+ Timestamptz: {
256
+ kind: 'typeConstructor',
257
+ args: [{ kind: 'number', name: 'precision', integer: true, minimum: 0, optional: true }],
258
+ output: {
259
+ codecId: 'pg/timestamptz@1',
260
+ nativeType: 'timestamptz',
261
+ typeParams: { precision: { kind: 'arg', index: 0 } },
262
+ },
263
+ },
264
+ Time: {
265
+ kind: 'typeConstructor',
266
+ args: [{ kind: 'number', name: 'precision', integer: true, minimum: 0, optional: true }],
267
+ output: {
268
+ codecId: 'pg/time@1',
269
+ nativeType: 'time',
270
+ typeParams: { precision: { kind: 'arg', index: 0 } },
271
+ },
272
+ },
273
+ Timetz: {
274
+ kind: 'typeConstructor',
275
+ args: [{ kind: 'number', name: 'precision', integer: true, minimum: 0, optional: true }],
276
+ output: {
277
+ codecId: 'pg/timetz@1',
278
+ nativeType: 'timetz',
279
+ typeParams: { precision: { kind: 'arg', index: 0 } },
280
+ },
281
+ },
282
+ Uuid: { kind: 'typeConstructor', output: { codecId: 'pg/uuid@1', nativeType: 'uuid' } },
283
+ SmallInt: { kind: 'typeConstructor', output: { codecId: 'pg/int2@1', nativeType: 'int2' } },
284
+ Real: { kind: 'typeConstructor', output: { codecId: 'pg/float4@1', nativeType: 'float4' } },
285
+ Date: { kind: 'typeConstructor', output: { codecId: 'pg/date@1', nativeType: 'date' } },
286
+ } as const satisfies AuthoringTypeNamespace;
287
+
288
+ export const postgresAuthoringTypes = {
289
+ ...postgresScalarAuthoringTypes,
290
+ ...postgresNativeAuthoringTypes,
291
+ } as const satisfies AuthoringTypeNamespace;
167
292
 
168
293
  export function createPostgresDefaultFunctionRegistry(): ReadonlyMap<
169
294
  string,
@@ -178,27 +303,8 @@ export function createPostgresMutationDefaultGeneratorDescriptors(): readonly Mu
178
303
  ({ id, applicableCodecIds }): MutationDefaultGeneratorDescriptor => ({
179
304
  id,
180
305
  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
306
  }),
197
307
  ),
198
308
  timestampNowControlDescriptor(),
199
309
  ];
200
310
  }
201
-
202
- export function createPostgresScalarTypeDescriptors(): ReadonlyMap<string, string> {
203
- return new Map(postgresScalarTypeDescriptors);
204
- }
@@ -5,18 +5,23 @@ import {
5
5
  type AggregateExpr,
6
6
  type AnyExpression,
7
7
  type AnyFromSource,
8
+ type AnyJsonValueProjection,
8
9
  type AnyParamRef,
9
10
  type AnyQueryAst,
10
11
  type BinaryExpr,
12
+ type CaseExpr,
13
+ type CastExpr,
11
14
  type ColumnRef,
12
15
  collectOrderedParamRefs,
13
16
  type DeleteAst,
17
+ type FunctionCallExpr,
14
18
  type InsertAst,
15
19
  type InsertValue,
16
20
  type JoinAst,
17
21
  type JoinOnExpr,
18
22
  type JsonArrayAggExpr,
19
23
  type JsonObjectExpr,
24
+ type JsonValueProjectionVisitor,
20
25
  type ListExpression,
21
26
  LiteralExpr,
22
27
  type LoweredParam,
@@ -32,7 +37,12 @@ import {
32
37
  type UpdateAst,
33
38
  type WindowFuncExpr,
34
39
  } from '@prisma-next/sql-relational-core/ast';
35
- import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
40
+ import { isPgEnumParams } from '@prisma-next/target-postgres/codecs';
41
+ import {
42
+ escapeLiteral,
43
+ quoteIdentifier,
44
+ quoteQualifiedName,
45
+ } from '@prisma-next/target-postgres/sql-utils';
36
46
  import { ifDefined } from '@prisma-next/utils/defined';
37
47
  import type { PostgresContract } from './types';
38
48
 
@@ -89,7 +99,7 @@ function renderTypedParam(
89
99
  `Postgres lowering: ParamRef carries codecId "${codecId}" but the ` +
90
100
  'assembled codec lookup has no entry for it. This usually indicates ' +
91
101
  'a missing extension pack in the runtime stack — register the pack ' +
92
- 'that contributes this codec (e.g. `extensionPacks: [pgvectorRuntime]`), ' +
102
+ 'that contributes this codec (e.g. `extensions: [pgvectorRuntime]`), ' +
93
103
  'or use the codec directly from `@prisma-next/target-postgres/codecs` ' +
94
104
  "if it's a builtin.",
95
105
  );
@@ -107,6 +117,15 @@ function renderTypedParam(
107
117
  const nativeType = isRecord(dialectBlock) ? dialectBlock['nativeType'] : undefined;
108
118
  if (typeof nativeType === 'string') {
109
119
  const arraySuffix = many ? '[]' : '';
120
+ // A `typeParams.typeName` marks a named database type (e.g. a native
121
+ // enum): cast to its quoted, schema-qualified identifier so Postgres does
122
+ // not case-fold it (`$1::HoldType` would resolve as `holdtype`). Mirrors
123
+ // the DDL-side policy in `buildColumnTypeSql`. Builtin spellings
124
+ // (`double precision`, `jsonb`, …) stay verbatim — quoting them would
125
+ // turn them into (nonexistent) user-type lookups.
126
+ if (isPgEnumParams(typeParams)) {
127
+ return `$${index}::${quoteQualifiedName(nativeType)}${arraySuffix}`;
128
+ }
110
129
  if (!POSTGRES_INFERRABLE_NATIVE_TYPES.has(nativeType)) {
111
130
  return `$${index}::${nativeType}${arraySuffix}`;
112
131
  }
@@ -121,6 +140,19 @@ function isRecord(value: unknown): value is Record<string, unknown> {
121
140
  return typeof value === 'object' && value !== null;
122
141
  }
123
142
 
143
+ function unreachableKind(value: never): string {
144
+ const candidate: unknown = value;
145
+ if (
146
+ typeof candidate === 'object' &&
147
+ candidate !== null &&
148
+ 'kind' in candidate &&
149
+ typeof candidate.kind === 'string'
150
+ ) {
151
+ return candidate.kind;
152
+ }
153
+ return 'unknown';
154
+ }
155
+
124
156
  /**
125
157
  * Per-render carrier threaded through every helper. Bundles the param-index map (for `$N` numbering) and the assembled-stack `codecLookup` (for cast policy at the `renderTypedParam` chokepoint). Carrying both on a single value keeps helper signatures stable.
126
158
  */
@@ -169,9 +201,7 @@ export function renderLoweredSql(
169
201
  break;
170
202
  // v8 ignore next 4
171
203
  default:
172
- throw new Error(
173
- `Unsupported AST node kind: ${(node satisfies never as { kind: string }).kind}`,
174
- );
204
+ throw new Error(`Unsupported AST node kind: ${unreachableKind(node)}`);
175
205
  }
176
206
 
177
207
  return Object.freeze({ sql, params: Object.freeze(params) });
@@ -465,13 +495,17 @@ function renderSource(
465
495
  case 'function-source': {
466
496
  const args = node.args.map((arg) => renderExpr(arg, contract, pim)).join(', ');
467
497
  const call = `${node.fn}(${args})`;
468
- return node.alias !== undefined ? `${call} AS ${quoteIdentifier(node.alias)}` : call;
498
+ const ordinality = node.ordinality ? ' WITH ORDINALITY' : '';
499
+ const alias = node.alias === undefined ? '' : ` AS ${quoteIdentifier(node.alias)}`;
500
+ const columnAliases =
501
+ node.columnAliases === undefined
502
+ ? ''
503
+ : `(${node.columnAliases.map((column) => quoteIdentifier(column)).join(', ')})`;
504
+ return `${call}${ordinality}${alias}${columnAliases}`;
469
505
  }
470
506
  // v8 ignore next 4
471
507
  default:
472
- throw new Error(
473
- `Unsupported source node kind: ${(node satisfies never as { kind: string }).kind}`,
474
- );
508
+ throw new Error(`Unsupported source node kind: ${unreachableKind(node)}`);
475
509
  }
476
510
  }
477
511
 
@@ -518,6 +552,9 @@ function isAtomicExpressionKind(kind: AnyExpression['kind']): boolean {
518
552
  case 'literal':
519
553
  case 'aggregate':
520
554
  case 'window-func':
555
+ case 'function-call':
556
+ case 'cast':
557
+ case 'case':
521
558
  case 'json-object':
522
559
  case 'json-array-agg':
523
560
  case 'list':
@@ -644,6 +681,44 @@ function renderWindowFuncExpr(
644
681
  return `${fn}(${args}) OVER (${over})`;
645
682
  }
646
683
 
684
+ function renderFunctionCallExpr(
685
+ expr: FunctionCallExpr,
686
+ contract: PostgresContract,
687
+ pim: ParamIndexMap,
688
+ ): string {
689
+ const args = expr.args.map((arg) => renderExpr(arg, contract, pim)).join(', ');
690
+ return `${expr.fn}(${args})`;
691
+ }
692
+
693
+ function renderCastExpr(expr: CastExpr, contract: PostgresContract, pim: ParamIndexMap): string {
694
+ return `CAST(${renderExpr(expr.expr, contract, pim)} AS ${expr.targetType})`;
695
+ }
696
+
697
+ function renderCaseExpr(expr: CaseExpr, contract: PostgresContract, pim: ParamIndexMap): string {
698
+ const branches = expr.branches
699
+ .map(
700
+ (branch) =>
701
+ `WHEN ${renderExpr(branch.condition, contract, pim)} THEN ${renderExpr(branch.value, contract, pim)}`,
702
+ )
703
+ .join(' ');
704
+ const elseClause =
705
+ expr.elseExpr === undefined ? '' : ` ELSE ${renderExpr(expr.elseExpr, contract, pim)}`;
706
+ return `CASE ${branches}${elseClause} END`;
707
+ }
708
+
709
+ function renderJsonValueProjection(
710
+ projection: AnyJsonValueProjection,
711
+ contract: PostgresContract,
712
+ pim: ParamIndexMap,
713
+ ): string {
714
+ const visitor: JsonValueProjectionVisitor<string> = {
715
+ codec: ({ value }) => renderExpr(value, contract, pim),
716
+ native: ({ value }) => renderExpr(value, contract, pim),
717
+ document: ({ value }) => renderExpr(value, contract, pim),
718
+ };
719
+ return projection.accept(visitor);
720
+ }
721
+
647
722
  function renderJsonObjectExpr(
648
723
  expr: JsonObjectExpr,
649
724
  contract: PostgresContract,
@@ -652,10 +727,7 @@ function renderJsonObjectExpr(
652
727
  const args = expr.entries
653
728
  .flatMap((entry): [string, string] => {
654
729
  const key = `'${escapeLiteral(entry.key)}'`;
655
- if (entry.value.kind === 'literal') {
656
- return [key, renderLiteral(entry.value)];
657
- }
658
- return [key, renderExpr(entry.value, contract, pim)];
730
+ return [key, renderJsonValueProjection(entry.value, contract, pim)];
659
731
  })
660
732
  .join(', ');
661
733
  return `json_build_object(${args})`;
@@ -680,7 +752,7 @@ function renderJsonArrayAggExpr(
680
752
  expr.orderBy && expr.orderBy.length > 0
681
753
  ? ` ORDER BY ${renderOrderByItems(expr.orderBy, contract, pim)}`
682
754
  : '';
683
- const aggregated = `json_agg(${renderExpr(expr.expr, contract, pim)}${aggregateOrderBy})`;
755
+ const aggregated = `json_agg(${renderJsonValueProjection(expr.expr, contract, pim)}${aggregateOrderBy})`;
684
756
  if (expr.onEmpty === 'emptyArray') {
685
757
  return `coalesce(${aggregated}, json_build_array())`;
686
758
  }
@@ -702,6 +774,12 @@ function renderExpr(expr: AnyExpression, contract: PostgresContract, pim: ParamI
702
774
  return renderAggregateExpr(node, contract, pim);
703
775
  case 'window-func':
704
776
  return renderWindowFuncExpr(node, contract, pim);
777
+ case 'function-call':
778
+ return renderFunctionCallExpr(node, contract, pim);
779
+ case 'cast':
780
+ return renderCastExpr(node, contract, pim);
781
+ case 'case':
782
+ return renderCaseExpr(node, contract, pim);
705
783
  case 'json-object':
706
784
  return renderJsonObjectExpr(node, contract, pim);
707
785
  case 'json-array-agg':
@@ -738,9 +816,7 @@ function renderExpr(expr: AnyExpression, contract: PostgresContract, pim: ParamI
738
816
  return renderRawExpr(node, contract, pim);
739
817
  // v8 ignore next 4
740
818
  default:
741
- throw new Error(
742
- `Unsupported expression node kind: ${(node satisfies never as { kind: string }).kind}`,
743
- );
819
+ throw new Error(`Unsupported expression node kind: ${unreachableKind(node)}`);
744
820
  }
745
821
  }
746
822
 
@@ -912,9 +988,7 @@ function renderInsertValue(
912
988
  return renderExpr(value, contract, pim);
913
989
  // v8 ignore next 4
914
990
  default:
915
- throw new Error(
916
- `Unsupported value node in INSERT: ${(value satisfies never as { kind: string }).kind}`,
917
- );
991
+ throw new Error(`Unsupported value node in INSERT: ${unreachableKind(value)}`);
918
992
  }
919
993
  }
920
994
 
@@ -979,9 +1053,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
979
1053
  }
980
1054
  // v8 ignore next 4
981
1055
  default:
982
- throw new Error(
983
- `Unsupported onConflict action: ${(action satisfies never as { kind: string }).kind}`,
984
- );
1056
+ throw new Error(`Unsupported onConflict action: ${unreachableKind(action)}`);
985
1057
  }
986
1058
  })()
987
1059
  : '';
@@ -10,13 +10,13 @@ import { PostgresControlAdapter } from '../core/control-adapter';
10
10
  import {
11
11
  createPostgresDefaultFunctionRegistry,
12
12
  createPostgresMutationDefaultGeneratorDescriptors,
13
- createPostgresScalarTypeDescriptors,
13
+ postgresAuthoringTypes,
14
14
  } from '../core/control-mutation-defaults';
15
15
  import { postgresAdapterDescriptorMeta } from '../core/descriptor-meta';
16
16
 
17
17
  const postgresAdapterDescriptor: SqlControlAdapterDescriptor<'postgres'> = {
18
18
  ...postgresAdapterDescriptorMeta,
19
- scalarTypeDescriptors: createPostgresScalarTypeDescriptors(),
19
+ authoring: { type: postgresAuthoringTypes, valueObjectStorageType: 'Jsonb' },
20
20
  controlMutationDefaults: {
21
21
  defaultFunctionRegistry: createPostgresDefaultFunctionRegistry(),
22
22
  generatorDescriptors: createPostgresMutationDefaultGeneratorDescriptors(),
@@ -39,7 +39,7 @@ const postgresRuntimeAdapterDescriptor: SqlRuntimeAdapterDescriptor<'postgres',
39
39
  create(stack): SqlRuntimeAdapter {
40
40
  // The runtime `ExecutionStack` does not (yet) carry a pre-assembled `codecLookup` field the way the control `ControlStack` does, so we derive an equivalent lookup here from the stack's component metadata (target + adapter + extension packs) using the same assembly helper that `createControlStack` uses. This keeps the renderer fed with the same codec set on both planes — including extension-contributed codecs like
41
41
  // `pg/vector@1` from `@prisma-next/extension-pgvector`.
42
- const components = [stack.target, stack.adapter, ...stack.extensionPacks];
42
+ const components = [stack.target, stack.adapter, ...stack.extensions];
43
43
  const codecLookup = extractCodecLookup(components);
44
44
  return createPostgresAdapter({ codecLookup });
45
45
  },