@prisma-next/adapter-postgres 0.12.0 → 0.13.0-dev.10

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.
@@ -0,0 +1,155 @@
1
+ import { REFERENTIAL_ACTION_SQL } from '@prisma-next/sql-contract/referential-action-sql';
2
+ import type {
3
+ DdlColumn,
4
+ DdlColumnDefaultVisitor,
5
+ DdlTableConstraint,
6
+ ForeignKeyConstraint,
7
+ FunctionColumnDefault,
8
+ LiteralColumnDefault,
9
+ PrimaryKeyConstraint,
10
+ UniqueConstraint,
11
+ } from '@prisma-next/sql-relational-core/ast';
12
+ import type {
13
+ PostgresCreateSchema,
14
+ PostgresCreateTable,
15
+ PostgresDdlNode,
16
+ PostgresDdlVisitor,
17
+ } from '@prisma-next/target-postgres/ddl';
18
+ import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
19
+ import type { PostgresLoweredStatement } from './types';
20
+
21
+ function quoteQualifiedIdentifier(name: string): string {
22
+ return name.split('.').map(quoteIdentifier).join('.');
23
+ }
24
+
25
+ function renderPrimaryKeyConstraint(constraint: PrimaryKeyConstraint): string {
26
+ const cols = constraint.columns.map(quoteIdentifier).join(', ');
27
+ if (constraint.name !== undefined) {
28
+ return `CONSTRAINT ${quoteIdentifier(constraint.name)} PRIMARY KEY (${cols})`;
29
+ }
30
+ return `PRIMARY KEY (${cols})`;
31
+ }
32
+
33
+ function renderForeignKeyConstraint(constraint: ForeignKeyConstraint): string {
34
+ const cols = constraint.columns.map(quoteIdentifier).join(', ');
35
+ const refCols = constraint.refColumns.map(quoteIdentifier).join(', ');
36
+ let sql = `FOREIGN KEY (${cols}) REFERENCES ${quoteQualifiedIdentifier(constraint.refTable)} (${refCols})`;
37
+ if (constraint.onDelete !== undefined) {
38
+ sql += ` ON DELETE ${REFERENTIAL_ACTION_SQL[constraint.onDelete]}`;
39
+ }
40
+ if (constraint.onUpdate !== undefined) {
41
+ sql += ` ON UPDATE ${REFERENTIAL_ACTION_SQL[constraint.onUpdate]}`;
42
+ }
43
+ if (constraint.name !== undefined) {
44
+ sql = `CONSTRAINT ${quoteIdentifier(constraint.name)} ${sql}`;
45
+ }
46
+ return sql;
47
+ }
48
+
49
+ function renderUniqueConstraint(constraint: UniqueConstraint): string {
50
+ const cols = constraint.columns.map(quoteIdentifier).join(', ');
51
+ if (constraint.name !== undefined) {
52
+ return `CONSTRAINT ${quoteIdentifier(constraint.name)} UNIQUE (${cols})`;
53
+ }
54
+ return `UNIQUE (${cols})`;
55
+ }
56
+
57
+ function renderTableConstraint(constraint: DdlTableConstraint): string {
58
+ switch (constraint.kind) {
59
+ case 'primary-key':
60
+ return renderPrimaryKeyConstraint(constraint);
61
+ case 'foreign-key':
62
+ return renderForeignKeyConstraint(constraint);
63
+ case 'unique':
64
+ return renderUniqueConstraint(constraint);
65
+ }
66
+ }
67
+
68
+ class PostgresDdlVisitorImpl implements PostgresDdlVisitor<string> {
69
+ createTable(node: PostgresCreateTable): string {
70
+ const ifNotExists = node.ifNotExists ? 'IF NOT EXISTS ' : '';
71
+ const tableRef = node.schema
72
+ ? `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`
73
+ : quoteIdentifier(node.table);
74
+ const columnDefs = node.columns.map((column) => renderColumn(column));
75
+ const constraintDefs =
76
+ node.constraints !== undefined ? node.constraints.map(renderTableConstraint) : [];
77
+ const allDefs = [...columnDefs, ...constraintDefs].join(',\n ');
78
+ return `CREATE TABLE ${ifNotExists}${tableRef} (\n ${allDefs}\n)`;
79
+ }
80
+
81
+ createSchema(node: PostgresCreateSchema): string {
82
+ const ifNotExists = node.ifNotExists ? 'IF NOT EXISTS ' : '';
83
+ return `CREATE SCHEMA ${ifNotExists}${quoteIdentifier(node.schema)}`;
84
+ }
85
+ }
86
+
87
+ // Postgres infers a quoted-literal default's type as `text` at parse time
88
+ // and applies an implicit text → target coercion at default-evaluation
89
+ // time. That coercion exists for some target types (text, jsonb, json)
90
+ // and not others (uuid, inet, timestamptz, citext, enums, user-defined
91
+ // types, geometry/geography from PostGIS, array types). Quoted-literal
92
+ // defaults on any non-text column therefore need an explicit
93
+ // `::<nativeType>` cast in the emitted DDL — both to state the default's
94
+ // intent (the literal IS that type, not text that happens to coerce) and
95
+ // to keep emitted migrations correct for types where no implicit cast
96
+ // exists. Numeric, boolean, and null literals are typed by Postgres
97
+ // directly (no `text` indirection) so they need no cast.
98
+ function isTextLikeNativeType(nativeType: string): boolean {
99
+ return (
100
+ nativeType === 'text' ||
101
+ nativeType === 'varchar' ||
102
+ nativeType.startsWith('varchar(') ||
103
+ nativeType === 'character varying' ||
104
+ nativeType.startsWith('character varying(') ||
105
+ nativeType === 'char' ||
106
+ nativeType.startsWith('char(') ||
107
+ nativeType === 'character' ||
108
+ nativeType.startsWith('character(')
109
+ );
110
+ }
111
+
112
+ const defaultVisitor: DdlColumnDefaultVisitor<string> = {
113
+ literal(node: LiteralColumnDefault, ctx): string {
114
+ const { value } = node;
115
+ if (typeof value === 'number' || typeof value === 'boolean') {
116
+ return `DEFAULT ${String(value)}`;
117
+ }
118
+ if (value === null) {
119
+ return 'DEFAULT NULL';
120
+ }
121
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
122
+ const literal = `'${escapeLiteral(serialized)}'`;
123
+ return isTextLikeNativeType(ctx.nativeType)
124
+ ? `DEFAULT ${literal}`
125
+ : `DEFAULT ${literal}::${ctx.nativeType}`;
126
+ },
127
+ function(node: FunctionColumnDefault, _ctx): string {
128
+ if (node.expression === 'autoincrement()') {
129
+ return '';
130
+ }
131
+ return `DEFAULT (${node.expression})`;
132
+ },
133
+ };
134
+
135
+ function renderColumn(column: DdlColumn): string {
136
+ const parts = [quoteIdentifier(column.name), column.type];
137
+ if (column.notNull) {
138
+ parts.push('NOT NULL');
139
+ }
140
+ if (column.primaryKey) {
141
+ parts.push('PRIMARY KEY');
142
+ }
143
+ const defaultClause = column.default
144
+ ? column.default.accept(defaultVisitor, { nativeType: column.type })
145
+ : '';
146
+ if (defaultClause.length > 0) {
147
+ parts.push(defaultClause);
148
+ }
149
+ return parts.join(' ');
150
+ }
151
+
152
+ export function renderLoweredDdl(ast: PostgresDdlNode): PostgresLoweredStatement {
153
+ const sql = ast.accept(new PostgresDdlVisitorImpl());
154
+ return Object.freeze({ sql, params: Object.freeze([]) });
155
+ }
@@ -1,4 +1,4 @@
1
- import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
1
+ import type { SqlControlDriverInstance } from '@prisma-next/sql-contract/types';
2
2
  import { PG_ENUM_CODEC_ID } from '@prisma-next/target-postgres/codec-ids';
3
3
 
4
4
  /**
@@ -117,7 +117,7 @@ function parseArrayElements(input: string): string[] {
117
117
  * (which writes them under `schema.annotations.pg.storageTypes`).
118
118
  */
119
119
  export async function introspectPostgresEnumTypes(options: {
120
- readonly driver: ControlDriverInstance<'sql', 'postgres'>;
120
+ readonly driver: SqlControlDriverInstance<'postgres'>;
121
121
  readonly schemaName?: string;
122
122
  }): Promise<Record<string, PostgresEnumStorageTypeAnnotation>> {
123
123
  const namespace = options.schemaName ?? 'public';
@@ -0,0 +1,124 @@
1
+ import type { SqlControlDriverInstance } from '@prisma-next/sql-contract/types';
2
+ import {
3
+ type AnyQueryAst,
4
+ type LoweredStatement,
5
+ RawExpr,
6
+ } from '@prisma-next/sql-relational-core/ast';
7
+ import {
8
+ createAstCodecRegistry,
9
+ deriveParamMetadata,
10
+ encodeParamsWithMetadata,
11
+ } from '@prisma-next/sql-runtime';
12
+ import { PG_TIMESTAMPTZ_CODEC_ID } from '@prisma-next/target-postgres/codec-ids';
13
+ import { postgresCodecRegistry } from '@prisma-next/target-postgres/codecs';
14
+ import {
15
+ int4,
16
+ int8,
17
+ jsonb,
18
+ pgTable,
19
+ text,
20
+ textArray,
21
+ timestamptz,
22
+ } from '@prisma-next/target-postgres/contract-free';
23
+
24
+ const CONTROL_CODECS = createAstCodecRegistry(postgresCodecRegistry);
25
+
26
+ export const marker = pgTable(
27
+ { name: 'marker', schema: 'prisma_contract' },
28
+ {
29
+ space: text(),
30
+ core_hash: text(),
31
+ profile_hash: text(),
32
+ contract_json: jsonb({ nullable: true }),
33
+ canonical_version: int4({ nullable: true }),
34
+ updated_at: timestamptz(),
35
+ app_tag: text({ nullable: true }),
36
+ meta: jsonb({ nullable: true }),
37
+ invariants: textArray(),
38
+ },
39
+ );
40
+
41
+ /**
42
+ * Writeable subset of the `prisma_contract.ledger` table. Omits the
43
+ * DB-generated `id` (bigserial) and `created_at` (default `now()`) so the
44
+ * insert path doesn't have to pass them.
45
+ */
46
+ export const ledger = pgTable(
47
+ { name: 'ledger', schema: 'prisma_contract' },
48
+ {
49
+ space: text(),
50
+ migration_name: text(),
51
+ migration_hash: text(),
52
+ origin_core_hash: text({ nullable: true }),
53
+ destination_core_hash: text(),
54
+ operations: jsonb(),
55
+ },
56
+ );
57
+
58
+ /**
59
+ * Read-side handle covering every column of `prisma_contract.ledger`,
60
+ * including the DB-generated `id` (for ORDER BY) and `created_at`.
61
+ */
62
+ export const ledgerReadShape = pgTable(
63
+ { name: 'ledger', schema: 'prisma_contract' },
64
+ {
65
+ id: int8(),
66
+ space: text(),
67
+ migration_name: text(),
68
+ migration_hash: text(),
69
+ origin_core_hash: text({ nullable: true }),
70
+ destination_core_hash: text(),
71
+ operations: jsonb(),
72
+ created_at: timestamptz(),
73
+ },
74
+ );
75
+
76
+ export const infoSchemaTables = pgTable(
77
+ { name: 'tables', schema: 'information_schema' },
78
+ { table_schema: text(), table_name: text() },
79
+ );
80
+
81
+ export const NOW = new RawExpr({
82
+ parts: ['now()'],
83
+ returns: { codecId: PG_TIMESTAMPTZ_CODEC_ID, nullable: false },
84
+ });
85
+
86
+ type Lower = (query: AnyQueryAst) => LoweredStatement;
87
+
88
+ type MarkerDriver = {
89
+ query<Row = Record<string, unknown>>(
90
+ sql: string,
91
+ params?: readonly unknown[],
92
+ ): Promise<{ readonly rows: ReadonlyArray<Row> }>;
93
+ };
94
+
95
+ export function mergeInvariants(
96
+ current: readonly string[],
97
+ incoming: readonly string[],
98
+ ): readonly string[] {
99
+ return [...new Set([...current, ...incoming])].sort();
100
+ }
101
+
102
+ export async function execute(
103
+ lower: Lower,
104
+ driver: MarkerDriver,
105
+ query: AnyQueryAst,
106
+ ): Promise<readonly Record<string, unknown>[]> {
107
+ 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
+ );
118
+ const result = await driver.query(lowered.sql, encoded);
119
+ return result.rows;
120
+ }
121
+
122
+ export type PostgresMarkerDriver = MarkerDriver;
123
+ export type PostgresMarkerLower = Lower;
124
+ export type PostgresMarkerWriteDriver = SqlControlDriverInstance<'postgres'>;
@@ -1,3 +1,4 @@
1
+ import type { JsonValue } from '@prisma-next/contract/types';
1
2
  import type { CodecLookup } from '@prisma-next/framework-components/codec';
2
3
  import { runtimeError } from '@prisma-next/framework-components/runtime';
3
4
  import {
@@ -27,9 +28,11 @@ import {
27
28
  type RawSqlExpr,
28
29
  type SelectAst,
29
30
  type SubqueryExpr,
31
+ type TableSource,
30
32
  type UpdateAst,
31
33
  type WindowFuncExpr,
32
34
  } from '@prisma-next/sql-relational-core/ast';
35
+ import { PostgresTableSource } from '@prisma-next/target-postgres/contract-free';
33
36
  import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
34
37
  import { ifDefined } from '@prisma-next/utils/defined';
35
38
  import type { PostgresContract } from './types';
@@ -189,10 +192,11 @@ function renderSelect(ast: SelectAst, contract: PostgresContract, pim: ParamInde
189
192
  ? `GROUP BY ${ast.groupBy.map((expr) => renderExpr(expr, contract, pim)).join(', ')}`
190
193
  : '';
191
194
  const havingClause = ast.having ? `HAVING ${renderWhere(ast.having, contract, pim)}` : '';
195
+ const sourcesByRef = collectTableSources(ast);
192
196
  const orderClause = ast.orderBy?.length
193
197
  ? `ORDER BY ${ast.orderBy
194
198
  .map((order) => {
195
- const expr = renderExpr(order.expr, contract, pim);
199
+ const expr = renderOrderByExpr(order.expr, sourcesByRef, contract, pim);
196
200
  return `${expr} ${order.dir.toUpperCase()}`;
197
201
  })
198
202
  .join(', ')}`
@@ -216,6 +220,124 @@ function renderSelect(ast: SelectAst, contract: PostgresContract, pim: ParamInde
216
220
  return clauses.trim();
217
221
  }
218
222
 
223
+ /**
224
+ * Storage coordinate a query-level table reference (alias or bare name) resolves to. The ORDER BY enum hook uses this to look a column-ref's storage column up and read its value-set.
225
+ */
226
+ interface TableSourceCoordinate {
227
+ readonly name: string;
228
+ readonly namespaceId: string | undefined;
229
+ }
230
+
231
+ /**
232
+ * Map a SELECT's table references (the FROM source and any JOIN sources) to their storage coordinate, keyed by the name a `ColumnRef.table` would carry (the alias when present, otherwise the table name). Derived-table sources are skipped — their columns are projected through a sub-select, not a base storage column, so the enum hook does not apply.
233
+ */
234
+ function collectTableSources(ast: SelectAst): ReadonlyMap<string, TableSourceCoordinate> {
235
+ const sources = new Map<string, TableSourceCoordinate>();
236
+ const add = (source: AnyFromSource): void => {
237
+ if (source.kind !== 'table-source') {
238
+ return;
239
+ }
240
+ const ref = source.alias ?? source.name;
241
+ sources.set(ref, { name: source.name, namespaceId: source.namespaceId });
242
+ };
243
+ add(ast.from);
244
+ for (const join of ast.joins ?? []) {
245
+ add(join.source);
246
+ }
247
+ return sources;
248
+ }
249
+
250
+ /**
251
+ * Ordered, codec-encoded values of the value-set a storage column restricts to, or `undefined` when the referenced column carries no value-set (the common, non-enum case). Resolves the column's storage coordinate from the SELECT's table sources, then the column's `valueSet` ref to the value-set's `values`.
252
+ */
253
+ function allStrings(values: readonly JsonValue[]): values is readonly string[] {
254
+ return values.every((value) => typeof value === 'string');
255
+ }
256
+
257
+ function resolveEnumOrderValues(
258
+ ref: ColumnRef,
259
+ sourcesByRef: ReadonlyMap<string, TableSourceCoordinate>,
260
+ contract: PostgresContract,
261
+ ): readonly JsonValue[] | undefined {
262
+ const source = sourcesByRef.get(ref.table);
263
+ if (source === undefined || source.namespaceId === undefined) {
264
+ return undefined;
265
+ }
266
+ const column =
267
+ contract.storage.namespaces[source.namespaceId]?.entries.table[source.name]?.columns[
268
+ ref.column
269
+ ];
270
+ const valueSet = column?.valueSet;
271
+ if (valueSet === undefined) {
272
+ return undefined;
273
+ }
274
+ return contract.storage.namespaces[valueSet.namespaceId]?.entries.valueSet?.[valueSet.name]
275
+ ?.values;
276
+ }
277
+
278
+ /**
279
+ * Ordered values for an unqualified ORDER BY column (an `identifier-ref`, the shape the sql-builder emits for `.orderBy('col')`). Scans every FROM/JOIN source for a column of that name. Resolves only when exactly one source has a column of that name and it carries a value-set; if more than one source has such a column the bare identifier is ambiguous (regardless of which are enum-backed), so it falls through to the plain column rendering.
280
+ */
281
+ function resolveEnumOrderValuesForIdentifier(
282
+ name: string,
283
+ sourcesByRef: ReadonlyMap<string, TableSourceCoordinate>,
284
+ contract: PostgresContract,
285
+ ): readonly JsonValue[] | undefined {
286
+ let matchedColumns = 0;
287
+ let resolved: readonly JsonValue[] | undefined;
288
+ for (const source of sourcesByRef.values()) {
289
+ if (source.namespaceId === undefined) {
290
+ continue;
291
+ }
292
+ const column =
293
+ contract.storage.namespaces[source.namespaceId]?.entries.table[source.name]?.columns[name];
294
+ if (column === undefined) {
295
+ continue;
296
+ }
297
+ matchedColumns += 1;
298
+ if (matchedColumns > 1) {
299
+ return undefined;
300
+ }
301
+ const valueSet = column.valueSet;
302
+ if (valueSet === undefined) {
303
+ return undefined;
304
+ }
305
+ resolved =
306
+ contract.storage.namespaces[valueSet.namespaceId]?.entries.valueSet?.[valueSet.name]?.values;
307
+ }
308
+ return resolved;
309
+ }
310
+
311
+ /**
312
+ * Render an ORDER BY expression. A column reference onto an enum-restricted column sorts by declaration order via `array_position(ARRAY[…]::text[], <col>)` over the value-set's ordered values (NULLs return `NULL` from `array_position`, sorting per the clause's default NULL handling). Both qualified `column-ref`s and the unqualified `identifier-ref`s the sql-builder emits for `.orderBy('col')` are intercepted. Every other expression renders unchanged.
313
+ */
314
+ function renderOrderByExpr(
315
+ expr: AnyExpression,
316
+ sourcesByRef: ReadonlyMap<string, TableSourceCoordinate>,
317
+ contract: PostgresContract,
318
+ pim: ParamIndexMap,
319
+ ): string {
320
+ // Only TEXT enums lower to a value-set whose ORDER BY rewrite ships in this
321
+ // slice. Numeric-enum ORDER BY is future work; until then a non-string
322
+ // value-set falls through to plain column rendering rather than emitting a
323
+ // wrong numeric-as-text ARRAY.
324
+ if (expr.kind === 'column-ref') {
325
+ const orderValues = resolveEnumOrderValues(expr, sourcesByRef, contract);
326
+ if (orderValues !== undefined && allStrings(orderValues)) {
327
+ const array = orderValues.map((value) => `'${escapeLiteral(value)}'`).join(', ');
328
+ return `array_position(ARRAY[${array}]::text[], ${renderColumn(expr)})`;
329
+ }
330
+ }
331
+ if (expr.kind === 'identifier-ref') {
332
+ const orderValues = resolveEnumOrderValuesForIdentifier(expr.name, sourcesByRef, contract);
333
+ if (orderValues !== undefined && allStrings(orderValues)) {
334
+ const array = orderValues.map((value) => `'${escapeLiteral(value)}'`).join(', ');
335
+ return `array_position(ARRAY[${array}]::text[], ${quoteIdentifier(expr.name)})`;
336
+ }
337
+ }
338
+ return renderExpr(expr, contract, pim);
339
+ }
340
+
219
341
  function renderProjection(
220
342
  projection: ReadonlyArray<ProjectionItem>,
221
343
  contract: PostgresContract,
@@ -269,6 +391,39 @@ function renderDistinctPrefix(
269
391
  return '';
270
392
  }
271
393
 
394
+ function qualifyTableFromNamespaceCoordinate(
395
+ table: Pick<TableSource, 'name' | 'namespaceId'>,
396
+ contract: PostgresContract,
397
+ ): string {
398
+ if (table instanceof PostgresTableSource && table.schema !== undefined) {
399
+ return `${quoteIdentifier(table.schema)}.${quoteIdentifier(table.name)}`;
400
+ }
401
+ if (table.namespaceId === undefined) {
402
+ return quoteIdentifier(table.name);
403
+ }
404
+ const namespace = contract.storage.namespaces[table.namespaceId];
405
+ if (namespace === undefined) {
406
+ throw new Error(
407
+ `Table "${table.name}" references namespace "${table.namespaceId}" which is not present as a Postgres schema on the contract`,
408
+ );
409
+ }
410
+ const qualifyTable = namespace.qualifyTable;
411
+ if (qualifyTable === undefined) {
412
+ throw new Error(
413
+ `Table "${table.name}" references namespace "${table.namespaceId}" which is not materialised as a Postgres schema on the contract`,
414
+ );
415
+ }
416
+ return qualifyTable.call(namespace, table.name);
417
+ }
418
+
419
+ function renderTableSource(source: TableSource, contract: PostgresContract): string {
420
+ const qualified = qualifyTableFromNamespaceCoordinate(source, contract);
421
+ if (!source.alias) {
422
+ return qualified;
423
+ }
424
+ return `${qualified} AS ${quoteIdentifier(source.alias)}`;
425
+ }
426
+
272
427
  function renderSource(
273
428
  source: AnyFromSource,
274
429
  contract: PostgresContract,
@@ -276,13 +431,8 @@ function renderSource(
276
431
  ): string {
277
432
  const node = source;
278
433
  switch (node.kind) {
279
- case 'table-source': {
280
- const table = quoteIdentifier(node.name);
281
- if (!node.alias) {
282
- return table;
283
- }
284
- return `${table} AS ${quoteIdentifier(node.alias)}`;
285
- }
434
+ case 'table-source':
435
+ return renderTableSource(node, contract);
286
436
  case 'derived-table-source':
287
437
  return `(${renderSelect(node.query, contract, pim)}) AS ${quoteIdentifier(node.alias)}`;
288
438
  // v8 ignore next 4
@@ -659,8 +809,9 @@ function renderJoinOn(on: JoinOnExpr, contract: PostgresContract, pim: ParamInde
659
809
  function getInsertColumnOrder(
660
810
  rows: ReadonlyArray<Record<string, InsertValue>>,
661
811
  contract: PostgresContract,
662
- tableName: string,
812
+ tableRef: Pick<TableSource, 'name' | 'namespaceId'>,
663
813
  ): string[] {
814
+ const tableName = tableRef.name;
664
815
  const orderedColumns: string[] = [];
665
816
  const seenColumns = new Set<string>();
666
817
 
@@ -678,14 +829,18 @@ function getInsertColumnOrder(
678
829
  return orderedColumns;
679
830
  }
680
831
 
681
- let table: { columns: Record<string, unknown> } | undefined;
682
- for (const ns of Object.values(contract.storage.namespaces)) {
683
- // Namespace.tables is Record<string, IRNode> at the interface level;
684
- // SQL family namespaces hold StorageTable instances which have .columns.
685
- const found = ns.tables[tableName] as { columns: Record<string, unknown> } | undefined;
686
- if (found !== undefined) {
687
- table = found;
688
- break;
832
+ let table: { columns: Readonly<Record<string, unknown>> } | undefined;
833
+ if (tableRef.namespaceId !== undefined) {
834
+ const ns = contract.storage.namespaces[tableRef.namespaceId];
835
+ table = ns?.entries.table[tableName];
836
+ }
837
+ if (table === undefined) {
838
+ for (const ns of Object.values(contract.storage.namespaces)) {
839
+ const found = ns.entries.table[tableName];
840
+ if (found !== undefined) {
841
+ table = found;
842
+ break;
843
+ }
689
844
  }
690
845
  }
691
846
  if (!table) {
@@ -694,7 +849,11 @@ function getInsertColumnOrder(
694
849
  return Object.keys(table.columns);
695
850
  }
696
851
 
697
- function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap): string {
852
+ function renderInsertValue(
853
+ value: InsertValue | undefined,
854
+ contract: PostgresContract,
855
+ pim: ParamIndexMap,
856
+ ): string {
698
857
  if (!value || value.kind === 'default-value') {
699
858
  return 'DEFAULT';
700
859
  }
@@ -705,6 +864,8 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
705
864
  return renderParamRef(value, pim);
706
865
  case 'column-ref':
707
866
  return renderColumn(value);
867
+ case 'raw-expr':
868
+ return renderExpr(value, contract, pim);
708
869
  // v8 ignore next 4
709
870
  default:
710
871
  throw new Error(
@@ -714,7 +875,7 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
714
875
  }
715
876
 
716
877
  function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamIndexMap): string {
717
- const table = quoteIdentifier(ast.table.name);
878
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
718
879
  const rows = ast.rows;
719
880
  if (rows.length === 0) {
720
881
  throw new Error('INSERT requires at least one row');
@@ -726,7 +887,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
726
887
  return `INSERT INTO ${table} DEFAULT VALUES`;
727
888
  }
728
889
 
729
- const defaultColumns = getInsertColumnOrder(rows, contract, ast.table.name);
890
+ const defaultColumns = getInsertColumnOrder(rows, contract, ast.table);
730
891
  if (defaultColumns.length === 0) {
731
892
  return `INSERT INTO ${table} VALUES ${rows.map(() => '()').join(', ')}`;
732
893
  }
@@ -738,11 +899,13 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
738
899
  .join(', ')}`;
739
900
  }
740
901
 
741
- const columnOrder = getInsertColumnOrder(rows, contract, ast.table.name);
902
+ const columnOrder = getInsertColumnOrder(rows, contract, ast.table);
742
903
  const columns = columnOrder.map((column) => quoteIdentifier(column));
743
904
  const values = rows
744
905
  .map((row) => {
745
- const renderedRow = columnOrder.map((column) => renderInsertValue(row[column], pim));
906
+ const renderedRow = columnOrder.map((column) =>
907
+ renderInsertValue(row[column], contract, pim),
908
+ );
746
909
  return `(${renderedRow.join(', ')})`;
747
910
  })
748
911
  .join(', ');
@@ -786,7 +949,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
786
949
  }
787
950
 
788
951
  function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamIndexMap): string {
789
- const table = quoteIdentifier(ast.table.name);
952
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
790
953
  const setEntries = Object.entries(ast.set);
791
954
  if (setEntries.length === 0) {
792
955
  throw new Error('UPDATE requires at least one SET assignment');
@@ -805,7 +968,7 @@ function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamInde
805
968
  }
806
969
 
807
970
  function renderDelete(ast: DeleteAst, contract: PostgresContract, pim: ParamIndexMap): string {
808
- const table = quoteIdentifier(ast.table.name);
971
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
809
972
  const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
810
973
  const returningClause = ast.returning?.length
811
974
  ? ` RETURNING ${renderReturning(ast.returning, contract, pim)}`
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapter-H8BiuXdq.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 MarkerReadResult,\n RawSqlLiteral,\n SqlQueryable,\n} from '@prisma-next/sql-relational-core/ast';\nimport type { RawCodecInferer } from '@prisma-next/sql-relational-core/expression';\nimport { parseContractMarkerRow } from '@prisma-next/sql-runtime';\nimport { createPostgresBuiltinCodecLookup } from './codec-lookup';\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 this.profile = Object.freeze({\n id: options?.profileId ?? 'postgres/default@1',\n target: 'postgres',\n capabilities: defaultCapabilities,\n readMarker: (queryable: SqlQueryable) => readPostgresMarker(queryable),\n });\n }\n\n lower(ast: AnyQueryAst, context: LowererContext<PostgresContract>): PostgresLoweredStatement {\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\nasync function readPostgresMarker(queryable: SqlQueryable): Promise<MarkerReadResult> {\n const exists = await queryable.query(\n 'select 1 from information_schema.tables where table_schema = $1 and table_name = $2',\n ['prisma_contract', 'marker'],\n );\n if (exists.rows.length === 0) {\n return { kind: 'no-table' };\n }\n\n const result = await queryable.query(\n 'select core_hash, profile_hash, contract_json, canonical_version, updated_at, app_tag, meta, invariants from prisma_contract.marker where space = $1',\n [APP_SPACE_ID],\n );\n const row = result.rows[0];\n if (!row) {\n return { kind: 'absent' };\n }\n // Postgres' driver hydrates `text[]` columns as native JS arrays, so the row is already in the shape the shared parser expects.\n return { kind: 'present', record: parseContractMarkerRow(row) };\n}\n\nexport function createPostgresAdapter(options?: PostgresAdapterOptions) {\n return Object.freeze(new PostgresAdapterImpl(options));\n}\n"],"mappings":";;;;AAiBA,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,KAAK,UAAU,OAAO,OAAO;GAC3B,IAAI,SAAS,aAAa;GAC1B,QAAQ;GACR,cAAc;GACd,aAAa,cAA4B,mBAAmB,SAAS;EACvE,CAAC;CACH;CAEA,MAAM,KAAkB,SAAqE;EAC3F,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,eAAe,mBAAmB,WAAoD;CAKpF,KAAI,MAJiB,UAAU,MAC7B,uFACA,CAAC,mBAAmB,QAAQ,CAC9B,GACW,KAAK,WAAW,GACzB,OAAO,EAAE,MAAM,WAAW;CAO5B,MAAM,OAAM,MAJS,UAAU,MAC7B,wJACA,CAAC,YAAY,CACf,GACmB,KAAK;CACxB,IAAI,CAAC,KACH,OAAO,EAAE,MAAM,SAAS;CAG1B,OAAO;EAAE,MAAM;EAAW,QAAQ,uBAAuB,GAAG;CAAE;AAChE;AAEA,SAAgB,sBAAsB,SAAkC;CACtE,OAAO,OAAO,OAAO,IAAI,oBAAoB,OAAO,CAAC;AACvD"}