@prisma-next/adapter-postgres 0.12.0-dev.5 → 0.12.0-dev.51

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,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'>;
@@ -27,9 +27,11 @@ import {
27
27
  type RawSqlExpr,
28
28
  type SelectAst,
29
29
  type SubqueryExpr,
30
+ type TableSource,
30
31
  type UpdateAst,
31
32
  type WindowFuncExpr,
32
33
  } from '@prisma-next/sql-relational-core/ast';
34
+ import { PostgresTableSource } from '@prisma-next/target-postgres/contract-free';
33
35
  import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
34
36
  import { ifDefined } from '@prisma-next/utils/defined';
35
37
  import type { PostgresContract } from './types';
@@ -269,6 +271,39 @@ function renderDistinctPrefix(
269
271
  return '';
270
272
  }
271
273
 
274
+ function qualifyTableFromNamespaceCoordinate(
275
+ table: Pick<TableSource, 'name' | 'namespaceId'>,
276
+ contract: PostgresContract,
277
+ ): string {
278
+ if (table instanceof PostgresTableSource && table.schema !== undefined) {
279
+ return `${quoteIdentifier(table.schema)}.${quoteIdentifier(table.name)}`;
280
+ }
281
+ if (table.namespaceId === undefined) {
282
+ return quoteIdentifier(table.name);
283
+ }
284
+ const namespace = contract.storage.namespaces[table.namespaceId];
285
+ if (namespace === undefined) {
286
+ throw new Error(
287
+ `Table "${table.name}" references namespace "${table.namespaceId}" which is not present as a Postgres schema on the contract`,
288
+ );
289
+ }
290
+ const qualifyTable = namespace.qualifyTable;
291
+ if (qualifyTable === undefined) {
292
+ throw new Error(
293
+ `Table "${table.name}" references namespace "${table.namespaceId}" which is not materialised as a Postgres schema on the contract`,
294
+ );
295
+ }
296
+ return qualifyTable.call(namespace, table.name);
297
+ }
298
+
299
+ function renderTableSource(source: TableSource, contract: PostgresContract): string {
300
+ const qualified = qualifyTableFromNamespaceCoordinate(source, contract);
301
+ if (!source.alias) {
302
+ return qualified;
303
+ }
304
+ return `${qualified} AS ${quoteIdentifier(source.alias)}`;
305
+ }
306
+
272
307
  function renderSource(
273
308
  source: AnyFromSource,
274
309
  contract: PostgresContract,
@@ -276,13 +311,8 @@ function renderSource(
276
311
  ): string {
277
312
  const node = source;
278
313
  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
- }
314
+ case 'table-source':
315
+ return renderTableSource(node, contract);
286
316
  case 'derived-table-source':
287
317
  return `(${renderSelect(node.query, contract, pim)}) AS ${quoteIdentifier(node.alias)}`;
288
318
  // v8 ignore next 4
@@ -659,8 +689,9 @@ function renderJoinOn(on: JoinOnExpr, contract: PostgresContract, pim: ParamInde
659
689
  function getInsertColumnOrder(
660
690
  rows: ReadonlyArray<Record<string, InsertValue>>,
661
691
  contract: PostgresContract,
662
- tableName: string,
692
+ tableRef: Pick<TableSource, 'name' | 'namespaceId'>,
663
693
  ): string[] {
694
+ const tableName = tableRef.name;
664
695
  const orderedColumns: string[] = [];
665
696
  const seenColumns = new Set<string>();
666
697
 
@@ -678,14 +709,18 @@ function getInsertColumnOrder(
678
709
  return orderedColumns;
679
710
  }
680
711
 
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;
712
+ let table: { columns: Readonly<Record<string, unknown>> } | undefined;
713
+ if (tableRef.namespaceId !== undefined) {
714
+ const ns = contract.storage.namespaces[tableRef.namespaceId];
715
+ table = ns?.entries.table[tableName];
716
+ }
717
+ if (table === undefined) {
718
+ for (const ns of Object.values(contract.storage.namespaces)) {
719
+ const found = ns.entries.table[tableName];
720
+ if (found !== undefined) {
721
+ table = found;
722
+ break;
723
+ }
689
724
  }
690
725
  }
691
726
  if (!table) {
@@ -694,7 +729,11 @@ function getInsertColumnOrder(
694
729
  return Object.keys(table.columns);
695
730
  }
696
731
 
697
- function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap): string {
732
+ function renderInsertValue(
733
+ value: InsertValue | undefined,
734
+ contract: PostgresContract,
735
+ pim: ParamIndexMap,
736
+ ): string {
698
737
  if (!value || value.kind === 'default-value') {
699
738
  return 'DEFAULT';
700
739
  }
@@ -705,6 +744,8 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
705
744
  return renderParamRef(value, pim);
706
745
  case 'column-ref':
707
746
  return renderColumn(value);
747
+ case 'raw-expr':
748
+ return renderExpr(value, contract, pim);
708
749
  // v8 ignore next 4
709
750
  default:
710
751
  throw new Error(
@@ -714,7 +755,7 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
714
755
  }
715
756
 
716
757
  function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamIndexMap): string {
717
- const table = quoteIdentifier(ast.table.name);
758
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
718
759
  const rows = ast.rows;
719
760
  if (rows.length === 0) {
720
761
  throw new Error('INSERT requires at least one row');
@@ -726,7 +767,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
726
767
  return `INSERT INTO ${table} DEFAULT VALUES`;
727
768
  }
728
769
 
729
- const defaultColumns = getInsertColumnOrder(rows, contract, ast.table.name);
770
+ const defaultColumns = getInsertColumnOrder(rows, contract, ast.table);
730
771
  if (defaultColumns.length === 0) {
731
772
  return `INSERT INTO ${table} VALUES ${rows.map(() => '()').join(', ')}`;
732
773
  }
@@ -738,11 +779,13 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
738
779
  .join(', ')}`;
739
780
  }
740
781
 
741
- const columnOrder = getInsertColumnOrder(rows, contract, ast.table.name);
782
+ const columnOrder = getInsertColumnOrder(rows, contract, ast.table);
742
783
  const columns = columnOrder.map((column) => quoteIdentifier(column));
743
784
  const values = rows
744
785
  .map((row) => {
745
- const renderedRow = columnOrder.map((column) => renderInsertValue(row[column], pim));
786
+ const renderedRow = columnOrder.map((column) =>
787
+ renderInsertValue(row[column], contract, pim),
788
+ );
746
789
  return `(${renderedRow.join(', ')})`;
747
790
  })
748
791
  .join(', ');
@@ -786,7 +829,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
786
829
  }
787
830
 
788
831
  function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamIndexMap): string {
789
- const table = quoteIdentifier(ast.table.name);
832
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
790
833
  const setEntries = Object.entries(ast.set);
791
834
  if (setEntries.length === 0) {
792
835
  throw new Error('UPDATE requires at least one SET assignment');
@@ -805,7 +848,7 @@ function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamInde
805
848
  }
806
849
 
807
850
  function renderDelete(ast: DeleteAst, contract: PostgresContract, pim: ParamIndexMap): string {
808
- const table = quoteIdentifier(ast.table.name);
851
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
809
852
  const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
810
853
  const returningClause = ast.returning?.length
811
854
  ? ` 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"}