@prisma-next/adapter-postgres 0.12.0-dev.4 → 0.12.0-dev.40

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 { ControlDriverInstance } from '@prisma-next/framework-components/control';
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 = ControlDriverInstance<'sql', '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
 
@@ -679,13 +710,22 @@ function getInsertColumnOrder(
679
710
  }
680
711
 
681
712
  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;
713
+ if (tableRef.namespaceId !== undefined) {
714
+ const ns = contract.storage.namespaces[tableRef.namespaceId];
715
+ const found = ns?.tables[tableName] as { columns: Record<string, unknown> } | undefined;
686
716
  if (found !== undefined) {
687
717
  table = found;
688
- break;
718
+ }
719
+ }
720
+ if (table === undefined) {
721
+ for (const ns of Object.values(contract.storage.namespaces)) {
722
+ // Namespace.tables is Record<string, IRNode> at the interface level;
723
+ // SQL family namespaces hold StorageTable instances which have .columns.
724
+ const found = ns.tables[tableName] as { columns: Record<string, unknown> } | undefined;
725
+ if (found !== undefined) {
726
+ table = found;
727
+ break;
728
+ }
689
729
  }
690
730
  }
691
731
  if (!table) {
@@ -694,7 +734,11 @@ function getInsertColumnOrder(
694
734
  return Object.keys(table.columns);
695
735
  }
696
736
 
697
- function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap): string {
737
+ function renderInsertValue(
738
+ value: InsertValue | undefined,
739
+ contract: PostgresContract,
740
+ pim: ParamIndexMap,
741
+ ): string {
698
742
  if (!value || value.kind === 'default-value') {
699
743
  return 'DEFAULT';
700
744
  }
@@ -705,6 +749,8 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
705
749
  return renderParamRef(value, pim);
706
750
  case 'column-ref':
707
751
  return renderColumn(value);
752
+ case 'raw-expr':
753
+ return renderExpr(value, contract, pim);
708
754
  // v8 ignore next 4
709
755
  default:
710
756
  throw new Error(
@@ -714,7 +760,7 @@ function renderInsertValue(value: InsertValue | undefined, pim: ParamIndexMap):
714
760
  }
715
761
 
716
762
  function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamIndexMap): string {
717
- const table = quoteIdentifier(ast.table.name);
763
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
718
764
  const rows = ast.rows;
719
765
  if (rows.length === 0) {
720
766
  throw new Error('INSERT requires at least one row');
@@ -726,7 +772,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
726
772
  return `INSERT INTO ${table} DEFAULT VALUES`;
727
773
  }
728
774
 
729
- const defaultColumns = getInsertColumnOrder(rows, contract, ast.table.name);
775
+ const defaultColumns = getInsertColumnOrder(rows, contract, ast.table);
730
776
  if (defaultColumns.length === 0) {
731
777
  return `INSERT INTO ${table} VALUES ${rows.map(() => '()').join(', ')}`;
732
778
  }
@@ -738,11 +784,13 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
738
784
  .join(', ')}`;
739
785
  }
740
786
 
741
- const columnOrder = getInsertColumnOrder(rows, contract, ast.table.name);
787
+ const columnOrder = getInsertColumnOrder(rows, contract, ast.table);
742
788
  const columns = columnOrder.map((column) => quoteIdentifier(column));
743
789
  const values = rows
744
790
  .map((row) => {
745
- const renderedRow = columnOrder.map((column) => renderInsertValue(row[column], pim));
791
+ const renderedRow = columnOrder.map((column) =>
792
+ renderInsertValue(row[column], contract, pim),
793
+ );
746
794
  return `(${renderedRow.join(', ')})`;
747
795
  })
748
796
  .join(', ');
@@ -786,7 +834,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
786
834
  }
787
835
 
788
836
  function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamIndexMap): string {
789
- const table = quoteIdentifier(ast.table.name);
837
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
790
838
  const setEntries = Object.entries(ast.set);
791
839
  if (setEntries.length === 0) {
792
840
  throw new Error('UPDATE requires at least one SET assignment');
@@ -805,7 +853,7 @@ function renderUpdate(ast: UpdateAst, contract: PostgresContract, pim: ParamInde
805
853
  }
806
854
 
807
855
  function renderDelete(ast: DeleteAst, contract: PostgresContract, pim: ParamIndexMap): string {
808
- const table = quoteIdentifier(ast.table.name);
856
+ const table = qualifyTableFromNamespaceCoordinate(ast.table, contract);
809
857
  const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract, pim)}` : '';
810
858
  const returningClause = ast.returning?.length
811
859
  ? ` 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"}