@prisma-next/adapter-postgres 0.14.0-dev.4 → 0.14.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.
package/package.json CHANGED
@@ -1,34 +1,35 @@
1
1
  {
2
2
  "name": "@prisma-next/adapter-postgres",
3
- "version": "0.14.0-dev.4",
3
+ "version": "0.14.0-dev.40",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "dependencies": {
8
- "@prisma-next/contract": "0.14.0-dev.4",
9
- "@prisma-next/contract-authoring": "0.14.0-dev.4",
10
- "@prisma-next/errors": "0.14.0-dev.4",
11
- "@prisma-next/family-sql": "0.14.0-dev.4",
12
- "@prisma-next/framework-components": "0.14.0-dev.4",
13
- "@prisma-next/ids": "0.14.0-dev.4",
14
- "@prisma-next/sql-contract": "0.14.0-dev.4",
15
- "@prisma-next/sql-contract-psl": "0.14.0-dev.4",
16
- "@prisma-next/sql-contract-ts": "0.14.0-dev.4",
17
- "@prisma-next/sql-operations": "0.14.0-dev.4",
18
- "@prisma-next/sql-relational-core": "0.14.0-dev.4",
19
- "@prisma-next/sql-runtime": "0.14.0-dev.4",
20
- "@prisma-next/sql-schema-ir": "0.14.0-dev.4",
21
- "@prisma-next/target-postgres": "0.14.0-dev.4",
22
- "@prisma-next/utils": "0.14.0-dev.4",
8
+ "@prisma-next/contract": "0.14.0-dev.40",
9
+ "@prisma-next/contract-authoring": "0.14.0-dev.40",
10
+ "@prisma-next/errors": "0.14.0-dev.40",
11
+ "@prisma-next/family-sql": "0.14.0-dev.40",
12
+ "@prisma-next/framework-components": "0.14.0-dev.40",
13
+ "@prisma-next/ids": "0.14.0-dev.40",
14
+ "@prisma-next/sql-contract": "0.14.0-dev.40",
15
+ "@prisma-next/sql-contract-psl": "0.14.0-dev.40",
16
+ "@prisma-next/sql-contract-ts": "0.14.0-dev.40",
17
+ "@prisma-next/sql-operations": "0.14.0-dev.40",
18
+ "@prisma-next/sql-relational-core": "0.14.0-dev.40",
19
+ "@prisma-next/sql-runtime": "0.14.0-dev.40",
20
+ "@prisma-next/sql-schema-ir": "0.14.0-dev.40",
21
+ "@prisma-next/target-postgres": "0.14.0-dev.40",
22
+ "@prisma-next/utils": "0.14.0-dev.40",
23
23
  "arktype": "^2.2.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@prisma-next/cli": "0.14.0-dev.4",
27
- "@prisma-next/driver-postgres": "0.14.0-dev.4",
28
- "@prisma-next/migration-tools": "0.14.0-dev.4",
29
- "@prisma-next/test-utils": "0.14.0-dev.4",
30
- "@prisma-next/tsconfig": "0.14.0-dev.4",
31
- "@prisma-next/tsdown": "0.14.0-dev.4",
26
+ "@prisma-next/cli": "0.14.0-dev.40",
27
+ "@prisma-next/driver-postgres": "0.14.0-dev.40",
28
+ "@prisma-next/migration-tools": "0.14.0-dev.40",
29
+ "@prisma-next/psl-parser": "0.14.0-dev.40",
30
+ "@prisma-next/test-utils": "0.14.0-dev.40",
31
+ "@prisma-next/tsconfig": "0.14.0-dev.40",
32
+ "@prisma-next/tsdown": "0.14.0-dev.40",
32
33
  "pathe": "^2.0.3",
33
34
  "tsdown": "0.22.1",
34
35
  "typescript": "5.9.3",
@@ -30,6 +30,7 @@ const defaultCapabilities = Object.freeze({
30
30
  returning: true,
31
31
  defaultInInsert: true,
32
32
  lateral: true,
33
+ scalarList: true,
33
34
  },
34
35
  });
35
36
 
@@ -34,8 +34,6 @@ import type {
34
34
  SqlForeignKeyIR,
35
35
  SqlIndexIR,
36
36
  SqlReferentialAction,
37
- SqlSchemaIR,
38
- SqlTableIR,
39
37
  SqlUniqueIR,
40
38
  } from '@prisma-next/sql-schema-ir/types';
41
39
  import {
@@ -45,14 +43,25 @@ import {
45
43
  import type {
46
44
  AddColumnAction,
47
45
  AlterTableActionVisitor,
46
+ DropDefaultAction,
48
47
  PostgresAlterTable,
48
+ PostgresCreatePolicy,
49
49
  PostgresCreateSchema,
50
50
  PostgresCreateTable,
51
51
  PostgresDdlNode,
52
+ PostgresDropPolicy,
53
+ RlsPolicyOperation,
52
54
  } from '@prisma-next/target-postgres/ddl';
53
55
  import { parsePostgresDefault } from '@prisma-next/target-postgres/default-normalizer';
54
56
  import { normalizeSchemaNativeType } from '@prisma-next/target-postgres/native-type-normalizer';
55
57
  import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
58
+ import {
59
+ PostgresDatabaseSchemaNode,
60
+ PostgresNamespaceSchemaNode,
61
+ PostgresPolicySchemaNode,
62
+ PostgresRoleSchemaNode,
63
+ PostgresTableSchemaNode,
64
+ } from '@prisma-next/target-postgres/types';
56
65
  import { blindCast } from '@prisma-next/utils/casts';
57
66
  import { ifDefined } from '@prisma-next/utils/defined';
58
67
  import { encodeControlQueryParams } from './control-codecs';
@@ -548,26 +557,51 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
548
557
  driver: SqlControlDriverInstance<'postgres'>,
549
558
  contract?: unknown,
550
559
  schema = 'public',
551
- ): Promise<SqlSchemaIR> {
560
+ ): Promise<PostgresDatabaseSchemaNode> {
552
561
  const declaredNamespaces = extractContractNamespaceIds(contract);
553
- const ir =
562
+ const resolvedSchemas =
554
563
  declaredNamespaces.length > 0
555
- ? await this.introspectNamespaces(driver, declaredNamespaces)
556
- : await this.introspectSchema(driver, schema);
557
- // Capture the list of non-system schemas so downstream planners
558
- // (e.g. `verifyPostgresNamespacePresence`) can determine which
559
- // contract-declared namespaces need a `CREATE SCHEMA` before the
560
- // table DDL.
564
+ ? await this.resolveNamespaceSchemas(driver, declaredNamespaces)
565
+ : [schema];
566
+
567
+ // Walk schemas sequentially: every introspectSchema call shares the one
568
+ // control connection, so a parallel walk only serialises behind the wire
569
+ // protocol and trips pg's "already executing a query" deprecation.
570
+ const namespaces: Record<string, PostgresNamespaceSchemaNode> = {};
571
+ let pgVersion = 'unknown';
572
+ for (const resolved of resolvedSchemas) {
573
+ const { namespace, pgVersion: version } = await this.introspectSchema(driver, resolved);
574
+ namespaces[resolved] = namespace;
575
+ pgVersion = version;
576
+ }
577
+
578
+ const roles = await this.introspectRoles(driver);
561
579
  const existingSchemas = await this.listExistingSchemas(driver);
562
- const annotations = ir.annotations ?? {};
563
- const pg = (annotations as { pg?: Record<string, unknown> }).pg ?? {};
564
- return {
565
- ...ir,
566
- annotations: {
567
- ...annotations,
568
- pg: { ...pg, existingSchemas },
569
- },
570
- };
580
+ return new PostgresDatabaseSchemaNode({
581
+ namespaces,
582
+ roles,
583
+ existingSchemas,
584
+ pgVersion,
585
+ });
586
+ }
587
+
588
+ /**
589
+ * Reads cluster-scoped database roles. Roles are not schema-qualified, so
590
+ * this is queried once for the whole database rather than per namespace.
591
+ */
592
+ private async introspectRoles(
593
+ driver: SqlControlDriverInstance<'postgres'>,
594
+ ): Promise<readonly PostgresRoleSchemaNode[]> {
595
+ const rolesResult = await driver.query<{ rolname: string }>(
596
+ `SELECT rolname
597
+ FROM pg_catalog.pg_roles
598
+ WHERE rolname NOT LIKE 'pg_%'
599
+ AND rolname != 'postgres'
600
+ ORDER BY rolname`,
601
+ );
602
+ return rolesResult.rows.map(
603
+ (row) => new PostgresRoleSchemaNode({ name: row.rolname, namespaceId: UNBOUND_NAMESPACE_ID }),
604
+ );
571
605
  }
572
606
 
573
607
  /**
@@ -593,16 +627,16 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
593
627
  }
594
628
 
595
629
  /**
596
- * Walks every declared namespace, resolving `UNBOUND_NAMESPACE_ID` to
597
- * the connection's `current_schema()`, and merges the per-schema results
598
- * into a single `SqlSchemaIR`. The merged `tables` map is flat (keyed by
599
- * table name) so callers that look up by `tableName` see every contract
600
- * table regardless of which namespace it lives in.
630
+ * Resolves the declared namespace ids to their live DDL schema names,
631
+ * mapping `UNBOUND_NAMESPACE_ID` to the connection's `current_schema()`
632
+ * and de-duplicating. The caller introspects one namespace node per
633
+ * resolved schema there is no flat cross-schema merge, so two schemas
634
+ * holding a same-named table no longer collide.
601
635
  */
602
- private async introspectNamespaces(
636
+ private async resolveNamespaceSchemas(
603
637
  driver: SqlControlDriverInstance<'postgres'>,
604
638
  namespaceIds: readonly string[],
605
- ): Promise<SqlSchemaIR> {
639
+ ): Promise<readonly string[]> {
606
640
  const resolvedSchemas: string[] = [];
607
641
  for (const id of namespaceIds) {
608
642
  if (id === UNBOUND_NAMESPACE_ID) {
@@ -614,46 +648,19 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
614
648
  resolvedSchemas.push(id);
615
649
  }
616
650
  }
617
- const uniqueSchemas = Array.from(new Set(resolvedSchemas));
618
-
619
- // Walk schemas sequentially: every introspectSchema call shares the one
620
- // control connection, so a parallel walk only serialises behind the wire
621
- // protocol and trips pg's "already executing a query" deprecation.
622
- const perSchema: SqlSchemaIR[] = [];
623
- for (const schema of uniqueSchemas) {
624
- perSchema.push(await this.introspectSchema(driver, schema));
625
- }
626
-
627
- const mergedTables: Record<string, SqlTableIR> = {};
628
- for (const ir of perSchema) {
629
- for (const [tableName, table] of Object.entries(ir.tables)) {
630
- mergedTables[tableName] = table;
631
- }
632
- }
633
-
634
- const firstAnnotations = perSchema[0]?.annotations;
635
- const firstPg =
636
- blindCast<Record<string, unknown> | undefined, 'pg annotation envelope index slot'>(
637
- firstAnnotations?.['pg'],
638
- ) ?? {};
639
- return {
640
- tables: mergedTables,
641
- ...ifDefined('annotations', {
642
- ...firstAnnotations,
643
- pg: { ...firstPg },
644
- }),
645
- };
651
+ return Array.from(new Set(resolvedSchemas));
646
652
  }
647
653
 
648
654
  /**
649
- * Introspects a single Postgres schema and returns a raw SqlSchemaIR
650
- * containing only the tables in that schema. Used by `introspect` as
655
+ * Introspects a single Postgres schema and returns the namespace node for
656
+ * that schema (its tables, their policies, and its native enum type names),
657
+ * alongside the cluster-scoped Postgres version. Used by `introspect` as
651
658
  * the per-namespace walk.
652
659
  */
653
660
  private async introspectSchema(
654
661
  driver: SqlControlDriverInstance<'postgres'>,
655
662
  schema: string,
656
- ): Promise<SqlSchemaIR> {
663
+ ): Promise<{ readonly namespace: PostgresNamespaceSchemaNode; readonly pgVersion: string }> {
657
664
  // Issue the schema-wide queries one at a time. A single control connection
658
665
  // serialises queries anyway, so Promise.all buys no parallelism here and
659
666
  // makes pg emit a "client is already executing a query" deprecation. One
@@ -895,7 +902,18 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
895
902
  constraints.add(row.constraint_name);
896
903
  }
897
904
 
898
- const tables: Record<string, SqlTableIR> = {};
905
+ const tableInputs: Record<
906
+ string,
907
+ {
908
+ name: string;
909
+ columns: Record<string, SqlColumnIR>;
910
+ primaryKey?: PrimaryKey;
911
+ foreignKeys: readonly SqlForeignKeyIR[];
912
+ uniques: readonly SqlUniqueIR[];
913
+ indexes: readonly SqlIndexIR[];
914
+ checks?: SqlCheckConstraintIRInput[];
915
+ }
916
+ > = {};
899
917
 
900
918
  for (const tableRow of tablesResult.rows) {
901
919
  const tableName = tableRow.table_name;
@@ -927,11 +945,22 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
927
945
  nativeType = colRow.udt_name || colRow.data_type;
928
946
  }
929
947
 
948
+ // Postgres reports array columns as data_type='ARRAY'; the element type
949
+ // is the `nativeType` string minus the trailing `[]`. Strip the suffix,
950
+ // normalize the element type to the canonical form (e.g. `integer` →
951
+ // `int4`), and record `many: true` so introspection consumers (verifier,
952
+ // psl-contract-infer) can reconstruct the full array type as needed.
953
+ const many = nativeType.endsWith('[]') ? true : undefined;
954
+ if (many) {
955
+ nativeType = normalizeSchemaNativeType(nativeType.slice(0, -2));
956
+ }
957
+
930
958
  columns[colRow.column_name] = {
931
959
  name: colRow.column_name,
932
960
  nativeType,
933
961
  nullable: colRow.is_nullable === 'YES',
934
962
  ...ifDefined('default', colRow.column_default ?? undefined),
963
+ ...ifDefined('many', many),
935
964
  };
936
965
  }
937
966
 
@@ -1069,7 +1098,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1069
1098
  }
1070
1099
  }
1071
1100
 
1072
- tables[tableName] = {
1101
+ tableInputs[tableName] = {
1073
1102
  name: tableName,
1074
1103
  columns,
1075
1104
  ...ifDefined('primaryKey', primaryKey),
@@ -1090,19 +1119,61 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1090
1119
  [schema],
1091
1120
  );
1092
1121
  const nativeEnumTypeNames = nativeEnumResult.rows.map((r) => r.typname);
1122
+ const policiesResult = await driver.query<{
1123
+ schemaname: string;
1124
+ tablename: string;
1125
+ policyname: string;
1126
+ cmd: string;
1127
+ roles: string[];
1128
+ qual: string | null;
1129
+ with_check: string | null;
1130
+ permissive: string;
1131
+ }>(
1132
+ `SELECT schemaname, tablename, policyname, cmd, roles, qual, with_check, permissive
1133
+ FROM pg_catalog.pg_policies
1134
+ WHERE schemaname = $1
1135
+ ORDER BY tablename, policyname`,
1136
+ [schema],
1137
+ );
1138
+ const policiesByTable = new Map<string, PostgresPolicySchemaNode[]>();
1139
+ for (const row of policiesResult.rows) {
1140
+ const operation = mapPgCmd(row.cmd);
1141
+ const policyRoles = [
1142
+ ...new Set(parsePgNameArray(row.roles).map((r) => r.toLowerCase())),
1143
+ ].sort();
1144
+ const permissive = row.permissive.toUpperCase() === 'PERMISSIVE';
1145
+ const hashSuffixMatch = /^(.+)_([0-9a-f]{8})$/.exec(row.policyname);
1146
+ const prefix = hashSuffixMatch?.[1] ?? row.policyname;
1147
+ const policy = new PostgresPolicySchemaNode({
1148
+ name: row.policyname,
1149
+ prefix,
1150
+ tableName: row.tablename,
1151
+ namespaceId: row.schemaname,
1152
+ operation,
1153
+ roles: policyRoles,
1154
+ ...(row.qual !== null ? { using: row.qual } : {}),
1155
+ ...(row.with_check !== null ? { withCheck: row.with_check } : {}),
1156
+ permissive,
1157
+ });
1158
+ const list = policiesByTable.get(row.tablename) ?? [];
1159
+ list.push(policy);
1160
+ policiesByTable.set(row.tablename, list);
1161
+ }
1093
1162
 
1094
- const annotations = {
1095
- pg: {
1096
- schema,
1097
- version: await this.getPostgresVersion(driver),
1098
- ...(nativeEnumTypeNames.length > 0 && { nativeEnumTypeNames }),
1099
- },
1100
- };
1163
+ const tables: Record<string, PostgresTableSchemaNode> = {};
1164
+ for (const [tableName, input] of Object.entries(tableInputs)) {
1165
+ tables[tableName] = new PostgresTableSchemaNode({
1166
+ ...input,
1167
+ policies: policiesByTable.get(tableName) ?? [],
1168
+ });
1169
+ }
1101
1170
 
1102
- return {
1171
+ const namespace = new PostgresNamespaceSchemaNode({
1172
+ schemaName: schema,
1103
1173
  tables,
1104
- annotations,
1105
- };
1174
+ nativeEnumTypeNames,
1175
+ });
1176
+ return { namespace, pgVersion: await this.getPostgresVersion(driver) };
1106
1177
  }
1107
1178
 
1108
1179
  /**
@@ -1117,6 +1188,52 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1117
1188
  }
1118
1189
  }
1119
1190
 
1191
+ /**
1192
+ * Normalises a `name[]` column value from `pg_policies.roles`.
1193
+ *
1194
+ * The `pg` client's type-parser registry handles `text[]` (OID 1009) but not
1195
+ * `name[]` (OID 1003). When the parser is absent the raw Postgres text-array
1196
+ * literal (`{role1,role2}`) is returned as a string instead of a JS array.
1197
+ * This function accepts either form and returns a plain string array.
1198
+ */
1199
+ function parsePgNameArray(value: unknown): string[] {
1200
+ if (Array.isArray(value)) {
1201
+ return value as string[];
1202
+ }
1203
+ if (typeof value !== 'string') {
1204
+ return [];
1205
+ }
1206
+ const trimmed = value.trim();
1207
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
1208
+ return [];
1209
+ }
1210
+ const inner = trimmed.slice(1, -1);
1211
+ if (inner === '') {
1212
+ return [];
1213
+ }
1214
+ return inner.split(',').map((s) => s.trim());
1215
+ }
1216
+
1217
+ /**
1218
+ * Maps `pg_policies.cmd` text values to the `RlsPolicyOperation` union.
1219
+ * The `pg_policies` view renders the internal command code as an uppercase
1220
+ * English keyword; this function lowercases to match the IR type.
1221
+ */
1222
+ function mapPgCmd(cmd: string): RlsPolicyOperation {
1223
+ switch (cmd.toUpperCase()) {
1224
+ case 'SELECT':
1225
+ return 'select';
1226
+ case 'INSERT':
1227
+ return 'insert';
1228
+ case 'UPDATE':
1229
+ return 'update';
1230
+ case 'DELETE':
1231
+ return 'delete';
1232
+ default:
1233
+ return 'all';
1234
+ }
1235
+ }
1236
+
1120
1237
  /**
1121
1238
  * Extracts the namespace coordinate ids declared on a contract's storage,
1122
1239
  * or returns an empty array when no contract (or no storage / namespaces)
@@ -1133,6 +1250,9 @@ function extractContractNamespaceIds(contract: unknown): readonly string[] {
1133
1250
  }
1134
1251
 
1135
1252
  function normalizeFormattedType(formattedType: string, dataType: string, udtName: string): string {
1253
+ if (formattedType.endsWith('[]')) {
1254
+ return `${normalizeFormattedType(formattedType.slice(0, -2), dataType, udtName)}[]`;
1255
+ }
1136
1256
  if (formattedType === 'integer') {
1137
1257
  return 'int4';
1138
1258
  }
@@ -1376,6 +1496,18 @@ function pgIsTextLikeNativeType(nativeType: string): boolean {
1376
1496
  );
1377
1497
  }
1378
1498
 
1499
+ function pgRenderArrayElement(el: unknown): string {
1500
+ if (el === null) return 'NULL';
1501
+ if (typeof el === 'number' || typeof el === 'boolean') return String(el);
1502
+ if (typeof el === 'string') return `'${escapeLiteral(el)}'`;
1503
+ return `'${escapeLiteral(JSON.stringify(el))}'`;
1504
+ }
1505
+
1506
+ function pgRenderArrayLiteral(elements: unknown[]): string {
1507
+ if (elements.length === 0) return "'{}'";
1508
+ return `ARRAY[${elements.map(pgRenderArrayElement).join(', ')}]`;
1509
+ }
1510
+
1379
1511
  function pgInlineLiteral(wire: unknown, nativeType: string): string {
1380
1512
  if (wire === null) return 'NULL';
1381
1513
  if (typeof wire === 'boolean') return wire ? 'true' : 'false';
@@ -1407,6 +1539,9 @@ function pgInlineLiteral(wire: unknown, nativeType: string): string {
1407
1539
  .join('');
1408
1540
  return `'\\x${hex}'::${nativeType}`;
1409
1541
  }
1542
+ if (Array.isArray(wire) && nativeType.endsWith('[]')) {
1543
+ return pgRenderArrayLiteral(wire);
1544
+ }
1410
1545
  if (typeof wire === 'object') {
1411
1546
  const quoted = `'${escapeLiteral(JSON.stringify(wire))}'`;
1412
1547
  return `${quoted}::${nativeType}`;
@@ -1477,6 +1612,9 @@ function pgRenderDdlConstraint(constraint: DdlTableConstraint): string {
1477
1612
  }
1478
1613
  return sql;
1479
1614
  }
1615
+ if (constraint.kind === 'check-expression') {
1616
+ return `CONSTRAINT ${quoteIdentifier(constraint.name)} CHECK (${constraint.expression})`;
1617
+ }
1480
1618
  const cols = constraint.columns.map(quoteIdentifier).join(', ');
1481
1619
  if (constraint.name !== undefined) {
1482
1620
  return `CONSTRAINT ${quoteIdentifier(constraint.name)} UNIQUE (${cols})`;
@@ -1524,6 +1662,9 @@ async function pgRenderAlterTable(
1524
1662
  const colFragment = await pgRenderDdlColumn(action.column, codecLookup);
1525
1663
  return `ADD COLUMN ${colFragment}`;
1526
1664
  },
1665
+ dropDefault(action: DropDefaultAction): Promise<string> {
1666
+ return Promise.resolve(`ALTER COLUMN ${quoteIdentifier(action.columnName)} DROP DEFAULT`);
1667
+ },
1527
1668
  };
1528
1669
  const actionSqls = await Promise.all(node.actions.map((a) => a.accept(actionVisitor)));
1529
1670
  return {
@@ -1532,6 +1673,37 @@ async function pgRenderAlterTable(
1532
1673
  };
1533
1674
  }
1534
1675
 
1676
+ const POLICY_OPERATION_SQL: Record<RlsPolicyOperation, string> = {
1677
+ select: 'SELECT',
1678
+ insert: 'INSERT',
1679
+ update: 'UPDATE',
1680
+ delete: 'DELETE',
1681
+ all: 'ALL',
1682
+ };
1683
+
1684
+ function pgRenderCreatePolicy(node: PostgresCreatePolicy): SqlExecuteRequest {
1685
+ const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
1686
+ const permissiveness = node.permissive ? 'PERMISSIVE' : 'RESTRICTIVE';
1687
+ const command = POLICY_OPERATION_SQL[node.operation];
1688
+ const roles = node.roles.length === 0 ? 'PUBLIC' : node.roles.join(', ');
1689
+ let sql = `CREATE POLICY ${quoteIdentifier(node.name)} ON ${tableRef} AS ${permissiveness} FOR ${command} TO ${roles}`;
1690
+ if (node.using !== undefined) {
1691
+ sql += ` USING (${node.using})`;
1692
+ }
1693
+ if (node.withCheck !== undefined) {
1694
+ sql += ` WITH CHECK (${node.withCheck})`;
1695
+ }
1696
+ return { sql, params: [] };
1697
+ }
1698
+
1699
+ function pgRenderDropPolicy(node: PostgresDropPolicy): SqlExecuteRequest {
1700
+ const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
1701
+ return {
1702
+ sql: `DROP POLICY ${quoteIdentifier(node.name)} ON ${tableRef}`,
1703
+ params: [],
1704
+ };
1705
+ }
1706
+
1535
1707
  async function pgRenderDdlExecuteRequest(
1536
1708
  ast: PostgresDdlNode,
1537
1709
  codecLookup: CodecLookup,
@@ -1540,6 +1712,8 @@ async function pgRenderDdlExecuteRequest(
1540
1712
  createTable: (node: PostgresCreateTable) => pgRenderCreateTable(node, codecLookup),
1541
1713
  createSchema: (node: PostgresCreateSchema) => Promise.resolve(pgRenderCreateSchema(node)),
1542
1714
  alterTable: (node: PostgresAlterTable) => pgRenderAlterTable(node, codecLookup),
1715
+ createPolicy: (node: PostgresCreatePolicy) => Promise.resolve(pgRenderCreatePolicy(node)),
1716
+ dropPolicy: (node: PostgresDropPolicy) => Promise.resolve(pgRenderDropPolicy(node)),
1543
1717
  };
1544
1718
  return ast.accept(visitor);
1545
1719
  }
@@ -172,6 +172,7 @@ export const postgresAdapterDescriptorMeta = {
172
172
  returning: true,
173
173
  defaultInInsert: true,
174
174
  lateral: true,
175
+ scalarList: true,
175
176
  },
176
177
  },
177
178
  types: {
@@ -73,6 +73,7 @@ function renderTypedParam(
73
73
  index: number,
74
74
  codecId: string | undefined,
75
75
  codecLookup: CodecLookup,
76
+ many?: boolean,
76
77
  ): string {
77
78
  if (codecId === undefined) {
78
79
  return `$${index}`;
@@ -97,8 +98,14 @@ function renderTypedParam(
97
98
  const sqlBlock = isRecord(dbRecord) ? dbRecord['sql'] : undefined;
98
99
  const dialectBlock = isRecord(sqlBlock) ? sqlBlock['postgres'] : undefined;
99
100
  const nativeType = isRecord(dialectBlock) ? dialectBlock['nativeType'] : undefined;
100
- if (typeof nativeType === 'string' && !POSTGRES_INFERRABLE_NATIVE_TYPES.has(nativeType)) {
101
- return `$${index}::${nativeType}`;
101
+ if (typeof nativeType === 'string') {
102
+ const arraySuffix = many ? '[]' : '';
103
+ if (!POSTGRES_INFERRABLE_NATIVE_TYPES.has(nativeType)) {
104
+ return `$${index}::${nativeType}${arraySuffix}`;
105
+ }
106
+ if (many) {
107
+ return `$${index}::${nativeType}${arraySuffix}`;
108
+ }
102
109
  }
103
110
  return `$${index}`;
104
111
  }
@@ -736,7 +743,7 @@ function renderParamRef(ref: AnyParamRef, pim: ParamIndexMap): string {
736
743
  throw new Error('ParamRef not found in index map');
737
744
  }
738
745
  if (ref.kind === 'prepared-param-ref') {
739
- return renderTypedParam(index, ref.codec.codecId, pim.codecLookup);
746
+ return renderTypedParam(index, ref.codec.codecId, pim.codecLookup, ref.codec.many);
740
747
  }
741
748
  if (ref.codec === undefined) {
742
749
  throw runtimeError(
@@ -747,7 +754,7 @@ function renderParamRef(ref: AnyParamRef, pim: ParamIndexMap): string {
747
754
  { paramIndex: index, ...ifDefined('name', ref.name) },
748
755
  );
749
756
  }
750
- return renderTypedParam(index, ref.codec.codecId, pim.codecLookup);
757
+ return renderTypedParam(index, ref.codec.codecId, pim.codecLookup, ref.codec.many);
751
758
  }
752
759
 
753
760
  function renderLiteral(expr: LiteralExpr): string {
package/src/core/types.ts CHANGED
@@ -1,6 +1,5 @@
1
- import type { Contract } from '@prisma-next/contract/types';
2
1
  import type { CodecRegistry } from '@prisma-next/framework-components/codec';
3
- import type { SqlStorage, StorageColumn, StorageTable } from '@prisma-next/sql-contract/types';
2
+ import type { StorageColumn, StorageTable } from '@prisma-next/sql-contract/types';
4
3
  import type {
5
4
  AnyQueryAst,
6
5
  BinaryExpr,
@@ -32,7 +31,7 @@ export interface PostgresAdapterOptions {
32
31
  readonly codecLookup?: CodecRegistry;
33
32
  }
34
33
 
35
- export type PostgresContract = Contract<SqlStorage> & { readonly target: 'postgres' };
34
+ export type { PostgresContract } from '@prisma-next/target-postgres/types';
36
35
 
37
36
  export type Expr = ColumnRef | ParamRef | DefaultValueExpr;
38
37
 
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapter-CwkcdpM_.mjs","names":[],"sources":["../src/core/adapter.ts"],"sourcesContent":["import type { CodecRegistry } 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 { 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: CodecRegistry;\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 throw new Error(\n 'lower() does not lower DDL on the runtime adapter — DDL lowering is a control-plane concern handled by the control adapter.',\n );\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":";;;;AAkBA,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,MAAM,IAAI,MACR,6HACF;EAEF,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"}