@prisma-next/adapter-postgres 0.13.0-dev.14 → 0.13.0-dev.16

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.
@@ -14,6 +14,7 @@ import type { CodecLookup } from '@prisma-next/framework-components/codec';
14
14
  import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
15
15
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
16
16
  import { ledgerOriginFromStored } from '@prisma-next/migration-tools/ledger-origin';
17
+ import { REFERENTIAL_ACTION_SQL } from '@prisma-next/sql-contract/referential-action-sql';
17
18
  import type {
18
19
  PostgresEnumStorageEntry,
19
20
  SqlControlDriverInstance,
@@ -21,10 +22,16 @@ import type {
21
22
  } from '@prisma-next/sql-contract/types';
22
23
  import type {
23
24
  AnyQueryAst,
25
+ CodecRef,
26
+ DdlColumn,
24
27
  DdlNode,
28
+ DdlTableConstraint,
29
+ FunctionColumnDefault,
30
+ LiteralColumnDefault,
25
31
  LoweredStatement,
26
32
  LowererContext,
27
33
  MarkerReadResult,
34
+ SqlExecuteRequest,
28
35
  } from '@prisma-next/sql-relational-core/ast';
29
36
  import { isDdlNode } from '@prisma-next/sql-relational-core/ast';
30
37
  import type {
@@ -42,7 +49,11 @@ import {
42
49
  buildControlTableBootstrapQueries,
43
50
  buildSignMarkerBootstrapQueries,
44
51
  } from '@prisma-next/target-postgres/contract-free';
45
- import type { PostgresDdlNode } from '@prisma-next/target-postgres/ddl';
52
+ import type {
53
+ PostgresCreateSchema,
54
+ PostgresCreateTable,
55
+ PostgresDdlNode,
56
+ } from '@prisma-next/target-postgres/ddl';
46
57
  import { parsePostgresDefault } from '@prisma-next/target-postgres/default-normalizer';
47
58
  import {
48
59
  createResolveExistingEnumValues,
@@ -50,10 +61,11 @@ import {
50
61
  readPostgresSchemaIrAnnotations,
51
62
  } from '@prisma-next/target-postgres/enum-planning';
52
63
  import { normalizeSchemaNativeType } from '@prisma-next/target-postgres/native-type-normalizer';
64
+ import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
53
65
  import { blindCast } from '@prisma-next/utils/casts';
54
66
  import { ifDefined } from '@prisma-next/utils/defined';
55
67
  import { createPostgresBuiltinCodecLookup } from './codec-lookup';
56
- import { renderLoweredDdl } from './ddl-renderer';
68
+ import { encodeControlQueryParams } from './control-codecs';
57
69
  import {
58
70
  introspectPostgresEnumTypes,
59
71
  type PostgresEnumStorageTypeAnnotation,
@@ -156,11 +168,39 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
156
168
  */
157
169
  lower(ast: AnyQueryAst | PostgresDdlNode, context: LowererContext<unknown>): LoweredStatement {
158
170
  if (isDdlNode(ast)) {
159
- return renderLoweredDdl(ast);
171
+ throw new Error(
172
+ 'lower() cannot lower DDL: DDL default literals require inline codec encoding, which is async. Use lowerToExecuteRequest().',
173
+ );
160
174
  }
161
175
  return renderLoweredSql(ast, context.contract as PostgresContract, this.codecLookup);
162
176
  }
163
177
 
178
+ /**
179
+ * Lower an AST all the way to a driver-ready statement. For DDL nodes,
180
+ * literal column defaults are formatted as inline SQL with proper quoting and
181
+ * `::nativeType` cast suffixes. For query ASTs, params are kept as `$N`
182
+ * placeholders; wire values go in `params`. Does NOT call `this.lower()` —
183
+ * independent implementation.
184
+ */
185
+ async lowerToExecuteRequest(
186
+ ast: AnyQueryAst | PostgresDdlNode,
187
+ context?: LowererContext<unknown>,
188
+ ): Promise<SqlExecuteRequest> {
189
+ if (isDdlNode(ast)) {
190
+ return pgRenderDdlExecuteRequest(
191
+ blindCast<PostgresDdlNode, 'isDdlNode guard'>(ast),
192
+ this.codecLookup,
193
+ );
194
+ }
195
+ const lowered = renderLoweredSql(
196
+ ast,
197
+ blindCast<PostgresContract, 'Caller must supply matching contract'>(context?.contract),
198
+ this.codecLookup,
199
+ );
200
+ const params = await encodeControlQueryParams(lowered, ast);
201
+ return { sql: lowered.sql, params };
202
+ }
203
+
164
204
  /**
165
205
  * Reads the contract marker from `prisma_contract.marker`. Probes
166
206
  * `information_schema.tables` first so a fresh database (where the
@@ -1366,3 +1406,167 @@ function extractQuotedLiterals(listBody: string): readonly string[] | undefined
1366
1406
  const values = [...listBody.matchAll(pattern)].map((m) => (m[1] ?? '').replace(/''/g, "'"));
1367
1407
  return values.length > 0 ? values : undefined;
1368
1408
  }
1409
+
1410
+ // ---------------------------------------------------------------------------
1411
+ // pgRenderDdlExecuteRequest — independent DDL walker for lowerToExecuteRequest
1412
+ // ---------------------------------------------------------------------------
1413
+
1414
+ function pgIsTextLikeNativeType(nativeType: string): boolean {
1415
+ return (
1416
+ nativeType === 'text' ||
1417
+ nativeType === 'varchar' ||
1418
+ nativeType.startsWith('varchar(') ||
1419
+ nativeType === 'character varying' ||
1420
+ nativeType.startsWith('character varying(') ||
1421
+ nativeType === 'char' ||
1422
+ nativeType.startsWith('char(') ||
1423
+ nativeType === 'character' ||
1424
+ nativeType.startsWith('character(')
1425
+ );
1426
+ }
1427
+
1428
+ function pgInlineLiteral(wire: unknown, nativeType: string): string {
1429
+ if (wire === null) return 'NULL';
1430
+ if (typeof wire === 'boolean') return wire ? 'true' : 'false';
1431
+ if (typeof wire === 'number') {
1432
+ if (!Number.isFinite(wire)) {
1433
+ throw new Error(
1434
+ `pgRenderDdlExecuteRequest: non-finite number wire value ${String(wire)} cannot be emitted as a DEFAULT literal for native type "${nativeType}"`,
1435
+ );
1436
+ }
1437
+ return String(wire);
1438
+ }
1439
+ if (typeof wire === 'bigint') return String(wire);
1440
+ if (wire instanceof Date) {
1441
+ if (Number.isNaN(wire.getTime())) {
1442
+ throw new Error(
1443
+ `pgRenderDdlExecuteRequest: invalid Date value cannot be emitted as a DEFAULT literal for native type "${nativeType}"`,
1444
+ );
1445
+ }
1446
+ const quoted = `'${escapeLiteral(wire.toISOString())}'`;
1447
+ return pgIsTextLikeNativeType(nativeType) ? quoted : `${quoted}::${nativeType}`;
1448
+ }
1449
+ if (typeof wire === 'string') {
1450
+ const quoted = `'${escapeLiteral(wire)}'`;
1451
+ return pgIsTextLikeNativeType(nativeType) ? quoted : `${quoted}::${nativeType}`;
1452
+ }
1453
+ if (wire instanceof Uint8Array) {
1454
+ const hex = Array.from(wire)
1455
+ .map((b) => b.toString(16).padStart(2, '0'))
1456
+ .join('');
1457
+ return `'\\x${hex}'::${nativeType}`;
1458
+ }
1459
+ if (typeof wire === 'object') {
1460
+ const quoted = `'${escapeLiteral(JSON.stringify(wire))}'`;
1461
+ return `${quoted}::${nativeType}`;
1462
+ }
1463
+ throw new Error(
1464
+ `pgRenderDdlExecuteRequest: unexpected wire type "${typeof wire}" for native type "${nativeType}"`,
1465
+ );
1466
+ }
1467
+
1468
+ async function pgRenderDdlColumnDefault(
1469
+ def: LiteralColumnDefault | FunctionColumnDefault,
1470
+ nativeType: string,
1471
+ codecLookup: CodecLookup,
1472
+ codecRef: CodecRef | undefined,
1473
+ ): Promise<string> {
1474
+ if (def.kind === 'function') {
1475
+ if (def.expression === 'autoincrement()') return '';
1476
+ return `DEFAULT (${def.expression})`;
1477
+ }
1478
+ if (codecRef !== undefined) {
1479
+ const codec = codecLookup.get(codecRef.codecId);
1480
+ if (codec !== undefined) {
1481
+ const wire = await codec.encode(def.value, {});
1482
+ return `DEFAULT ${pgInlineLiteral(wire, nativeType)}`;
1483
+ }
1484
+ }
1485
+ // Fallback: codec-less literal defaults follow RawSqlLiteral wire-scalar semantics.
1486
+ return `DEFAULT ${pgInlineLiteral(def.value, nativeType)}`;
1487
+ }
1488
+
1489
+ async function pgRenderDdlColumn(column: DdlColumn, codecLookup: CodecLookup): Promise<string> {
1490
+ const parts = [quoteIdentifier(column.name), column.type];
1491
+ if (column.notNull) parts.push('NOT NULL');
1492
+ if (column.primaryKey) parts.push('PRIMARY KEY');
1493
+ if (column.default) {
1494
+ const clause = await pgRenderDdlColumnDefault(
1495
+ column.default,
1496
+ column.type,
1497
+ codecLookup,
1498
+ column.codecRef,
1499
+ );
1500
+ if (clause.length > 0) parts.push(clause);
1501
+ }
1502
+ return parts.join(' ');
1503
+ }
1504
+
1505
+ function pgRenderDdlConstraint(constraint: DdlTableConstraint): string {
1506
+ if (constraint.kind === 'primary-key') {
1507
+ const cols = constraint.columns.map(quoteIdentifier).join(', ');
1508
+ if (constraint.name !== undefined) {
1509
+ return `CONSTRAINT ${quoteIdentifier(constraint.name)} PRIMARY KEY (${cols})`;
1510
+ }
1511
+ return `PRIMARY KEY (${cols})`;
1512
+ }
1513
+ if (constraint.kind === 'foreign-key') {
1514
+ const cols = constraint.columns.map(quoteIdentifier).join(', ');
1515
+ const refTable = constraint.refTable.split('.').map(quoteIdentifier).join('.');
1516
+ const refCols = constraint.refColumns.map(quoteIdentifier).join(', ');
1517
+ let sql = `FOREIGN KEY (${cols}) REFERENCES ${refTable} (${refCols})`;
1518
+ if (constraint.onDelete !== undefined) {
1519
+ sql += ` ON DELETE ${REFERENTIAL_ACTION_SQL[constraint.onDelete]}`;
1520
+ }
1521
+ if (constraint.onUpdate !== undefined) {
1522
+ sql += ` ON UPDATE ${REFERENTIAL_ACTION_SQL[constraint.onUpdate]}`;
1523
+ }
1524
+ if (constraint.name !== undefined) {
1525
+ sql = `CONSTRAINT ${quoteIdentifier(constraint.name)} ${sql}`;
1526
+ }
1527
+ return sql;
1528
+ }
1529
+ const cols = constraint.columns.map(quoteIdentifier).join(', ');
1530
+ if (constraint.name !== undefined) {
1531
+ return `CONSTRAINT ${quoteIdentifier(constraint.name)} UNIQUE (${cols})`;
1532
+ }
1533
+ return `UNIQUE (${cols})`;
1534
+ }
1535
+
1536
+ async function pgRenderCreateTable(
1537
+ node: PostgresCreateTable,
1538
+ codecLookup: CodecLookup,
1539
+ ): Promise<SqlExecuteRequest> {
1540
+ const ifNotExists = node.ifNotExists ? 'IF NOT EXISTS ' : '';
1541
+ const tableRef = node.schema
1542
+ ? `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`
1543
+ : quoteIdentifier(node.table);
1544
+ const columnDefs = await Promise.all(
1545
+ node.columns.map((col) => pgRenderDdlColumn(col, codecLookup)),
1546
+ );
1547
+ const constraintDefs =
1548
+ node.constraints !== undefined ? node.constraints.map(pgRenderDdlConstraint) : [];
1549
+ const allDefs = [...columnDefs, ...constraintDefs].join(',\n ');
1550
+ return {
1551
+ sql: `CREATE TABLE ${ifNotExists}${tableRef} (\n ${allDefs}\n)`,
1552
+ params: [],
1553
+ };
1554
+ }
1555
+
1556
+ function pgRenderCreateSchema(node: PostgresCreateSchema): SqlExecuteRequest {
1557
+ const ifNotExists = node.ifNotExists ? 'IF NOT EXISTS ' : '';
1558
+ return {
1559
+ sql: `CREATE SCHEMA ${ifNotExists}${quoteIdentifier(node.schema)}`,
1560
+ params: [],
1561
+ };
1562
+ }
1563
+
1564
+ async function pgRenderDdlExecuteRequest(
1565
+ ast: PostgresDdlNode,
1566
+ codecLookup: CodecLookup,
1567
+ ): Promise<SqlExecuteRequest> {
1568
+ if (ast.kind === 'create-table') {
1569
+ return pgRenderCreateTable(blindCast<PostgresCreateTable, 'kind guard'>(ast), codecLookup);
1570
+ }
1571
+ return pgRenderCreateSchema(blindCast<PostgresCreateSchema, 'kind guard'>(ast));
1572
+ }
@@ -0,0 +1,25 @@
1
+ import type {
2
+ AnyQueryAst,
3
+ ContractCodecRegistry,
4
+ LoweredStatement,
5
+ } from '@prisma-next/sql-relational-core/ast';
6
+ import {
7
+ createAstCodecRegistry,
8
+ deriveParamMetadata,
9
+ encodeParamsWithMetadata,
10
+ } from '@prisma-next/sql-runtime';
11
+ import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
12
+
13
+ export const CONTROL_CODECS = createAstCodecRegistry(postgresCodecRegistry);
14
+
15
+ export async function encodeControlQueryParams(
16
+ lowered: LoweredStatement,
17
+ ast: AnyQueryAst,
18
+ codecs: ContractCodecRegistry = CONTROL_CODECS,
19
+ ): Promise<readonly unknown[]> {
20
+ const values = lowered.params.map((slot) => {
21
+ if (slot.kind === 'literal') return slot.value;
22
+ throw new Error(`control query lowered to a bind slot '${slot.name}', which is unsupported`);
23
+ });
24
+ return encodeParamsWithMetadata(values, deriveParamMetadata(ast), {}, codecs);
25
+ }
@@ -4,13 +4,7 @@ import {
4
4
  type LoweredStatement,
5
5
  RawExpr,
6
6
  } from '@prisma-next/sql-relational-core/ast';
7
- import {
8
- createAstCodecRegistry,
9
- deriveParamMetadata,
10
- encodeParamsWithMetadata,
11
- } from '@prisma-next/sql-runtime';
12
7
  import { PG_TIMESTAMPTZ_CODEC_ID } from '@prisma-next/target-postgres/codec-ids';
13
- import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
14
8
  import {
15
9
  int4,
16
10
  int8,
@@ -20,8 +14,7 @@ import {
20
14
  textArray,
21
15
  timestamptz,
22
16
  } from '@prisma-next/target-postgres/contract-free';
23
-
24
- const CONTROL_CODECS = createAstCodecRegistry(postgresCodecRegistry);
17
+ import { encodeControlQueryParams } from './control-codecs';
25
18
 
26
19
  export const marker = pgTable(
27
20
  { name: 'marker', schema: 'prisma_contract' },
@@ -105,16 +98,7 @@ export async function execute(
105
98
  query: AnyQueryAst,
106
99
  ): Promise<readonly Record<string, unknown>[]> {
107
100
  const lowered = lower(query);
108
- const values = lowered.params.map((slot) => {
109
- if (slot.kind === 'literal') return slot.value;
110
- throw new Error('Postgres control DML lowered to a bind parameter, which is unsupported');
111
- });
112
- const encoded = await encodeParamsWithMetadata(
113
- values,
114
- deriveParamMetadata(query),
115
- {},
116
- CONTROL_CODECS,
117
- );
101
+ const encoded = await encodeControlQueryParams(lowered, query);
118
102
  const result = await driver.query(lowered.sql, encoded);
119
103
  return result.rows;
120
104
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapter-u3NV_jRl.mjs","names":[],"sources":["../src/core/adapter.ts"],"sourcesContent":["import type { CodecLookup } from '@prisma-next/framework-components/codec';\nimport { APP_SPACE_ID } from '@prisma-next/framework-components/control';\nimport type {\n Adapter,\n AdapterProfile,\n AnyQueryAst,\n LowererContext,\n RawSqlLiteral,\n SqlQueryable,\n} from '@prisma-next/sql-relational-core/ast';\nimport { isDdlNode } from '@prisma-next/sql-relational-core/ast';\nimport type { RawCodecInferer } from '@prisma-next/sql-relational-core/expression';\nimport type { PostgresDdlNode } from '@prisma-next/target-postgres/ddl';\nimport { createPostgresBuiltinCodecLookup } from './codec-lookup';\nimport { PostgresControlAdapter } from './control-adapter';\nimport { renderLoweredDdl } from './ddl-renderer';\nimport { renderLoweredSql } from './sql-renderer';\nimport type { PostgresAdapterOptions, PostgresContract, PostgresLoweredStatement } from './types';\n\nconst defaultCapabilities = Object.freeze({\n postgres: {\n orderBy: true,\n limit: true,\n lateral: true,\n jsonAgg: true,\n returning: true,\n distinctOn: true,\n },\n sql: {\n enums: true,\n returning: true,\n defaultInInsert: true,\n lateral: true,\n },\n});\n\nclass PostgresAdapterImpl\n implements Adapter<AnyQueryAst, PostgresContract, PostgresLoweredStatement>\n{\n // These fields make the adapter instance structurally compatible with RuntimeAdapterInstance<'sql', 'postgres'> without introducing a runtime-plane dependency.\n readonly familyId = 'sql' as const;\n readonly targetId = 'postgres' as const;\n\n readonly profile: AdapterProfile<'postgres'>;\n private readonly codecLookup: CodecLookup;\n\n constructor(options?: PostgresAdapterOptions) {\n this.codecLookup = options?.codecLookup ?? createPostgresBuiltinCodecLookup();\n const controlAdapter = new PostgresControlAdapter(this.codecLookup);\n this.profile = Object.freeze({\n id: options?.profileId ?? 'postgres/default@1',\n target: 'postgres',\n capabilities: defaultCapabilities,\n readMarker: (queryable: SqlQueryable) =>\n controlAdapter.readMarkerDiscriminated(\n {\n familyId: 'sql',\n targetId: 'postgres',\n query: async <Row = Record<string, unknown>>(\n sql: string,\n params?: readonly unknown[],\n ) => {\n const result = await queryable.query<Row>(sql, params);\n return { rows: [...result.rows] };\n },\n close: async () => {},\n },\n APP_SPACE_ID,\n ),\n });\n }\n\n lower(\n ast: AnyQueryAst | PostgresDdlNode,\n context: LowererContext<PostgresContract>,\n ): PostgresLoweredStatement {\n if (isDdlNode(ast)) {\n return renderLoweredDdl(ast);\n }\n return renderLoweredSql(ast, context.contract, this.codecLookup);\n }\n}\n\n/** Codec-id lookup for bare-literal interpolations used by `fns.raw` on a postgres client. Contributed as the descriptor's static `rawCodecInferer` slot. */\nexport const postgresRawCodecInferer: RawCodecInferer = {\n inferCodec(value: RawSqlLiteral): string {\n switch (typeof value) {\n case 'number':\n return Number.isSafeInteger(value) && value % 1 === 0 ? 'pg/int4' : 'pg/float8';\n case 'bigint':\n return 'pg/int8';\n case 'string':\n return 'pg/text';\n case 'boolean':\n return 'pg/bool';\n case 'object':\n if (value instanceof Uint8Array) return 'pg/bytea';\n }\n throw new Error(\n 'unsupported JS value type for raw-SQL interpolation: wrap this value in `param(...)` with an explicit codec',\n );\n },\n};\n\nexport function createPostgresAdapter(options?: PostgresAdapterOptions) {\n return Object.freeze(new PostgresAdapterImpl(options));\n}\n"],"mappings":";;;;AAmBA,MAAM,sBAAsB,OAAO,OAAO;CACxC,UAAU;EACR,SAAS;EACT,OAAO;EACP,SAAS;EACT,SAAS;EACT,WAAW;EACX,YAAY;CACd;CACA,KAAK;EACH,OAAO;EACP,WAAW;EACX,iBAAiB;EACjB,SAAS;CACX;AACF,CAAC;AAED,IAAM,sBAAN,MAEA;CAEE,WAAoB;CACpB,WAAoB;CAEpB;CACA;CAEA,YAAY,SAAkC;EAC5C,KAAK,cAAc,SAAS,eAAe,iCAAiC;EAC5E,MAAM,iBAAiB,IAAI,uBAAuB,KAAK,WAAW;EAClE,KAAK,UAAU,OAAO,OAAO;GAC3B,IAAI,SAAS,aAAa;GAC1B,QAAQ;GACR,cAAc;GACd,aAAa,cACX,eAAe,wBACb;IACE,UAAU;IACV,UAAU;IACV,OAAO,OACL,KACA,WACG;KAEH,OAAO,EAAE,MAAM,CAAC,IAAG,MADE,UAAU,MAAW,KAAK,MAAM,EAAA,CAC3B,IAAI,EAAE;IAClC;IACA,OAAO,YAAY,CAAC;GACtB,GACA,YACF;EACJ,CAAC;CACH;CAEA,MACE,KACA,SAC0B;EAC1B,IAAI,UAAU,GAAG,GACf,OAAO,iBAAiB,GAAG;EAE7B,OAAO,iBAAiB,KAAK,QAAQ,UAAU,KAAK,WAAW;CACjE;AACF;;AAGA,MAAa,0BAA2C,EACtD,WAAW,OAA8B;CACvC,QAAQ,OAAO,OAAf;EACE,KAAK,UACH,OAAO,OAAO,cAAc,KAAK,KAAK,QAAQ,MAAM,IAAI,YAAY;EACtE,KAAK,UACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,KAAK,UACH,IAAI,iBAAiB,YAAY,OAAO;CAC5C;CACA,MAAM,IAAI,MACR,6GACF;AACF,EACF;AAEA,SAAgB,sBAAsB,SAAkC;CACtE,OAAO,OAAO,OAAO,IAAI,oBAAoB,OAAO,CAAC;AACvD"}