@prisma-next/adapter-postgres 0.14.0-dev.10 → 0.14.0-dev.12

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.
@@ -1,12 +1,14 @@
1
+ import { SchemaDiffIssue } from "@prisma-next/framework-components/control";
1
2
  import { AnyQueryAst, DdlNode, LoweredStatement, LowererContext, MarkerReadResult, SqlExecuteRequest } from "@prisma-next/sql-relational-core/ast";
2
3
  import { parsePostgresDefault, parsePostgresDefault as parsePostgresDefault$1 } from "@prisma-next/target-postgres/default-normalizer";
3
4
  import { normalizeSchemaNativeType, normalizeSchemaNativeType as normalizeSchemaNativeType$1 } from "@prisma-next/target-postgres/native-type-normalizer";
4
5
  import { SqlEscapeError, escapeLiteral, qualifyName, quoteIdentifier } from "@prisma-next/target-postgres/sql-utils";
6
+ import { PostgresSchemaIR } from "@prisma-next/target-postgres/types";
5
7
  import { SqlControlAdapterDescriptor } from "@prisma-next/family-sql/control";
6
8
  import { PostgresDdlNode } from "@prisma-next/target-postgres/ddl";
7
- import { ContractMarkerRecord, LedgerEntryRecord } from "@prisma-next/contract/types";
9
+ import { Contract, ContractMarkerRecord, LedgerEntryRecord } from "@prisma-next/contract/types";
8
10
  import { CodecRegistry } from "@prisma-next/framework-components/codec";
9
- import { SqlControlDriverInstance } from "@prisma-next/sql-contract/types";
11
+ import { SqlControlDriverInstance, SqlStorage } from "@prisma-next/sql-contract/types";
10
12
  import { SqlControlAdapter } from "@prisma-next/family-sql/control-adapter";
11
13
  import { SqlSchemaIR } from "@prisma-next/sql-schema-ir/types";
12
14
 
@@ -41,6 +43,7 @@ declare class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
41
43
  * before comparison with contract native types.
42
44
  */
43
45
  readonly normalizeNativeType: typeof normalizeSchemaNativeType$1;
46
+ collectSchemaDiffIssues(contract: Contract<SqlStorage>, schema: SqlSchemaIR): readonly SchemaDiffIssue[];
44
47
  bootstrapControlTableQueries(): readonly DdlNode[];
45
48
  bootstrapSignMarkerQueries(): readonly DdlNode[];
46
49
  /**
@@ -148,7 +151,7 @@ declare class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
148
151
  * @param schema - Schema name to introspect when no contract is provided (defaults to 'public')
149
152
  * @returns Promise resolving to SqlSchemaIR representing the live database schema
150
153
  */
151
- introspect(driver: SqlControlDriverInstance<'postgres'>, contract?: unknown, schema?: string): Promise<SqlSchemaIR>;
154
+ introspect(driver: SqlControlDriverInstance<'postgres'>, contract?: unknown, schema?: string): Promise<PostgresSchemaIR>;
152
155
  /**
153
156
  * Lists every non-system schema present in the connected database.
154
157
  * The introspection consumer (`verifyPostgresNamespacePresence`)
@@ -1 +1 @@
1
- {"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/codec-lookup.ts","../src/core/control-adapter.ts","../src/exports/control.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;iBAWgB,gCAAA,IAAoC,aAAa;;;;;;;cC4EpD,sBAAA,YAAkC,iBAAA;EAAA,SACpC,QAAA;EAAA,SACA,QAAA;EAAA,iBAEQ,aAAA;cAEL,aAAA,EAAe,aAAA;;;;AAN7B;WAcW,gBAAA,SAAgB,sBAAA;;;;;;WAOhB,mBAAA,SAAmB,2BAAA;EAE5B,4BAAA,aAAyC,OAAA;EAIzC,0BAAA,aAAuC,OAAA;EAYY;;;;;;;;EAAnD,KAAA,CAAM,GAAA,EAAK,WAAA,GAAc,eAAA,EAAiB,OAAA,EAAS,cAAA,YAA0B,gBAAA;EAuD1E;;;;;;;EAjCG,qBAAA,CACJ,GAAA,EAAK,WAAA,GAAc,eAAA,EACnB,OAAA,GAAU,cAAA,YACT,OAAA,CAAQ,iBAAA;EAkHD;;;;;;;;EAvFJ,UAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,WACC,OAAA,CAAQ,oBAAA;EAKL,uBAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,WACC,OAAA,CAAQ,gBAAA;EAgQR;;;;;;EArPG,cAAA,CACJ,MAAA,EAAQ,wBAAA,eACP,OAAA,CAAQ,WAAA,SAAoB,oBAAA;EAAA,QAKjB,oBAAA;EAvHL;;;;;;EAgLH,UAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,YACC,OAAA,UAAiB,iBAAA;EAAA,QAKN,gBAAA;EApKL;;;;;EAwNH,YAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAoBG,UAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAnNkB;;;;EAsPf,YAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,UACA,YAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAhOD;;;;EA8PI,gBAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,UACA,KAAA;IAAA,SACW,MAAA;IAAA,SACA,IAAA;IAAA,SACA,EAAA;IAAA,SACA,aAAA;IAAA,SACA,aAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAAA,QAiBW,+BAAA;EAAA,QA0BA,gBAAA;EA9RiB;;;;;;;;;;;;;;;;;;;;;;;;EAwVzB,UAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,QAAA,YACA,MAAA,YACC,OAAA,CAAQ,WAAA;EAnMT;;;;;;;;EAAA,QAiOY,mBAAA;EAlLD;;;;;;;EAAA,QAuMC,oBAAA;EAlKD;;;;;EAAA,QAqNC,gBAAA;EA/MX;;;EAAA,QAypBW,kBAAA;AAAA;;;cCtkCV,yBAAA,EAA2B,2BAA2B"}
1
+ {"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/codec-lookup.ts","../src/core/control-adapter.ts","../src/exports/control.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;iBAWgB,gCAAA,IAAoC,aAAa;;;;;;;cC0FpD,sBAAA,YAAkC,iBAAA;EAAA,SACpC,QAAA;EAAA,SACA,QAAA;EAAA,iBAEQ,aAAA;cAEL,aAAA,EAAe,aAAA;;;;AAN7B;WAcW,gBAAA,SAAgB,sBAAA;;;;;;WAOhB,mBAAA,SAAmB,2BAAA;EAE5B,uBAAA,CACE,QAAA,EAAU,QAAA,CAAS,UAAA,GACnB,MAAA,EAAQ,WAAA,YACE,eAAA;EASZ,4BAAA,aAAyC,OAAA;EAIzC,0BAAA,aAAuC,OAAA;EAAA;;;;;;;;EAYvC,KAAA,CAAM,GAAA,EAAK,WAAA,GAAc,eAAA,EAAiB,OAAA,EAAS,cAAA,YAA0B,gBAAA;EAyB1E;;;;;;;EAHG,qBAAA,CACJ,GAAA,EAAK,WAAA,GAAc,eAAA,EACnB,OAAA,GAAU,cAAA,YACT,OAAA,CAAQ,iBAAA;EAmDoB;;;;;;;;EAxBzB,UAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,WACC,OAAA,CAAQ,oBAAA;EAKL,uBAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,WACC,OAAA,CAAQ,gBAAA;EA+MD;;;;;;EApMJ,cAAA,CACJ,MAAA,EAAQ,wBAAA,eACP,OAAA,CAAQ,WAAA,SAAoB,oBAAA;EAAA,QAKjB,oBAAA;EApIgD;;;;;;EA6LxD,UAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,YACC,OAAA,UAAiB,iBAAA;EAAA,QAKN,gBAAA;EA/LF;;;;;EAmPN,YAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAoBG,UAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAtPoC;;;;EAyRjC,YAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,UACA,YAAA,UACA,WAAA;IAAA,SACW,WAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EA/PI;;;;EA6RD,gBAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,KAAA,UACA,KAAA;IAAA,SACW,MAAA;IAAA,SACA,IAAA;IAAA,SACA,EAAA;IAAA,SACA,aAAA;IAAA,SACA,aAAA;IAAA,SACA,UAAA;EAAA,IAEV,OAAA;EAAA,QAiBW,+BAAA;EAAA,QA0BA,gBAAA;EA7SJ;;;;;;;;;;;;;;;;;;;;;;;;EAuWJ,UAAA,CACJ,MAAA,EAAQ,wBAAA,cACR,QAAA,YACA,MAAA,YACC,OAAA,CAAQ,gBAAA;EA5NE;;;;;;;;EAAA,QAsPC,mBAAA;EA1ND;;;;;;;EAAA,QA+OC,oBAAA;EArMD;;;;;EAAA,QA6PC,gBAAA;EA1NJ;;;EAAA,QA8sBI,kBAAA;AAAA;;;cC3oCV,yBAAA,EAA2B,2BAA2B"}
package/dist/control.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { r as createPostgresBuiltinCodecLookup, t as PostgresControlAdapter } from "./control-adapter-Dspz5uKp.mjs";
1
+ import { r as createPostgresBuiltinCodecLookup, t as PostgresControlAdapter } from "./control-adapter-DzXJMx3k.mjs";
2
2
  import { t as postgresAdapterDescriptorMeta } from "./descriptor-meta-DOgMfoqm.mjs";
3
3
  import { parsePostgresDefault } from "@prisma-next/target-postgres/default-normalizer";
4
4
  import { normalizeSchemaNativeType } from "@prisma-next/target-postgres/native-type-normalizer";
package/dist/runtime.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as postgresRawCodecInferer, t as createPostgresAdapter } from "./adapter-CwkcdpM_.mjs";
1
+ import { n as postgresRawCodecInferer, t as createPostgresAdapter } from "./adapter-BtKi7wFT.mjs";
2
2
  import { n as postgresQueryOperations, t as postgresAdapterDescriptorMeta } from "./descriptor-meta-DOgMfoqm.mjs";
3
3
  import { extractCodecLookup } from "@prisma-next/framework-components/control";
4
4
  import { postgresCodecRegistry } from "@prisma-next/target-postgres/codecs";
package/package.json CHANGED
@@ -1,34 +1,35 @@
1
1
  {
2
2
  "name": "@prisma-next/adapter-postgres",
3
- "version": "0.14.0-dev.10",
3
+ "version": "0.14.0-dev.12",
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.10",
9
- "@prisma-next/contract-authoring": "0.14.0-dev.10",
10
- "@prisma-next/errors": "0.14.0-dev.10",
11
- "@prisma-next/family-sql": "0.14.0-dev.10",
12
- "@prisma-next/framework-components": "0.14.0-dev.10",
13
- "@prisma-next/ids": "0.14.0-dev.10",
14
- "@prisma-next/sql-contract": "0.14.0-dev.10",
15
- "@prisma-next/sql-contract-psl": "0.14.0-dev.10",
16
- "@prisma-next/sql-contract-ts": "0.14.0-dev.10",
17
- "@prisma-next/sql-operations": "0.14.0-dev.10",
18
- "@prisma-next/sql-relational-core": "0.14.0-dev.10",
19
- "@prisma-next/sql-runtime": "0.14.0-dev.10",
20
- "@prisma-next/sql-schema-ir": "0.14.0-dev.10",
21
- "@prisma-next/target-postgres": "0.14.0-dev.10",
22
- "@prisma-next/utils": "0.14.0-dev.10",
8
+ "@prisma-next/contract": "0.14.0-dev.12",
9
+ "@prisma-next/contract-authoring": "0.14.0-dev.12",
10
+ "@prisma-next/errors": "0.14.0-dev.12",
11
+ "@prisma-next/family-sql": "0.14.0-dev.12",
12
+ "@prisma-next/framework-components": "0.14.0-dev.12",
13
+ "@prisma-next/ids": "0.14.0-dev.12",
14
+ "@prisma-next/sql-contract": "0.14.0-dev.12",
15
+ "@prisma-next/sql-contract-psl": "0.14.0-dev.12",
16
+ "@prisma-next/sql-contract-ts": "0.14.0-dev.12",
17
+ "@prisma-next/sql-operations": "0.14.0-dev.12",
18
+ "@prisma-next/sql-relational-core": "0.14.0-dev.12",
19
+ "@prisma-next/sql-runtime": "0.14.0-dev.12",
20
+ "@prisma-next/sql-schema-ir": "0.14.0-dev.12",
21
+ "@prisma-next/target-postgres": "0.14.0-dev.12",
22
+ "@prisma-next/utils": "0.14.0-dev.12",
23
23
  "arktype": "^2.2.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@prisma-next/cli": "0.14.0-dev.10",
27
- "@prisma-next/driver-postgres": "0.14.0-dev.10",
28
- "@prisma-next/migration-tools": "0.14.0-dev.10",
29
- "@prisma-next/test-utils": "0.14.0-dev.10",
30
- "@prisma-next/tsconfig": "0.14.0-dev.10",
31
- "@prisma-next/tsdown": "0.14.0-dev.10",
26
+ "@prisma-next/cli": "0.14.0-dev.12",
27
+ "@prisma-next/driver-postgres": "0.14.0-dev.12",
28
+ "@prisma-next/migration-tools": "0.14.0-dev.12",
29
+ "@prisma-next/psl-parser": "0.14.0-dev.12",
30
+ "@prisma-next/test-utils": "0.14.0-dev.12",
31
+ "@prisma-next/tsconfig": "0.14.0-dev.12",
32
+ "@prisma-next/tsdown": "0.14.0-dev.12",
32
33
  "pathe": "^2.0.3",
33
34
  "tsdown": "0.22.1",
34
35
  "typescript": "5.9.3",
@@ -1,4 +1,8 @@
1
- import type { ContractMarkerRecord, LedgerEntryRecord } from '@prisma-next/contract/types';
1
+ import type {
2
+ Contract,
3
+ ContractMarkerRecord,
4
+ LedgerEntryRecord,
5
+ } from '@prisma-next/contract/types';
2
6
  import {
3
7
  parseMarkerRowSafely,
4
8
  rethrowMarkerReadError,
@@ -7,11 +11,11 @@ import {
7
11
  import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
8
12
  import { parseContractMarkerRow } from '@prisma-next/family-sql/verify';
9
13
  import type { CodecLookup, CodecRegistry } from '@prisma-next/framework-components/codec';
10
- import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
14
+ import { APP_SPACE_ID, type SchemaDiffIssue } from '@prisma-next/framework-components/control';
11
15
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
12
16
  import { ledgerOriginFromStored } from '@prisma-next/migration-tools/ledger-origin';
13
17
  import { REFERENTIAL_ACTION_SQL } from '@prisma-next/sql-contract/referential-action-sql';
14
- import type { SqlControlDriverInstance } from '@prisma-next/sql-contract/types';
18
+ import type { SqlControlDriverInstance, SqlStorage } from '@prisma-next/sql-contract/types';
15
19
  import type {
16
20
  AnyQueryAst,
17
21
  CodecRef,
@@ -46,13 +50,23 @@ import type {
46
50
  AddColumnAction,
47
51
  AlterTableActionVisitor,
48
52
  PostgresAlterTable,
53
+ PostgresCreatePolicy,
49
54
  PostgresCreateSchema,
50
55
  PostgresCreateTable,
51
56
  PostgresDdlNode,
57
+ PostgresDropPolicy,
58
+ RlsPolicyOperation,
52
59
  } from '@prisma-next/target-postgres/ddl';
53
60
  import { parsePostgresDefault } from '@prisma-next/target-postgres/default-normalizer';
54
61
  import { normalizeSchemaNativeType } from '@prisma-next/target-postgres/native-type-normalizer';
62
+ import { diffPostgresRlsPolicies } from '@prisma-next/target-postgres/planner';
55
63
  import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
64
+ import {
65
+ isPostgresSchemaIR,
66
+ PostgresRlsPolicy,
67
+ PostgresRole,
68
+ PostgresSchemaIR,
69
+ } from '@prisma-next/target-postgres/types';
56
70
  import { blindCast } from '@prisma-next/utils/casts';
57
71
  import { ifDefined } from '@prisma-next/utils/defined';
58
72
  import { encodeControlQueryParams } from './control-codecs';
@@ -108,6 +122,18 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
108
122
  */
109
123
  readonly normalizeNativeType = normalizeSchemaNativeType;
110
124
 
125
+ collectSchemaDiffIssues(
126
+ contract: Contract<SqlStorage>,
127
+ schema: SqlSchemaIR,
128
+ ): readonly SchemaDiffIssue[] {
129
+ if (!isPostgresSchemaIR(schema)) {
130
+ throw new Error(
131
+ `RLS verification requires a PostgresSchemaIR; got ${(schema as { constructor?: { name?: string } }).constructor?.name ?? typeof schema}`,
132
+ );
133
+ }
134
+ return diffPostgresRlsPolicies({ contract, schema });
135
+ }
136
+
111
137
  bootstrapControlTableQueries(): readonly DdlNode[] {
112
138
  return buildControlTableBootstrapQueries();
113
139
  }
@@ -548,26 +574,22 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
548
574
  driver: SqlControlDriverInstance<'postgres'>,
549
575
  contract?: unknown,
550
576
  schema = 'public',
551
- ): Promise<SqlSchemaIR> {
577
+ ): Promise<PostgresSchemaIR> {
552
578
  const declaredNamespaces = extractContractNamespaceIds(contract);
553
579
  const ir =
554
580
  declaredNamespaces.length > 0
555
581
  ? await this.introspectNamespaces(driver, declaredNamespaces)
556
582
  : 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.
561
583
  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
- };
584
+ return new PostgresSchemaIR({
585
+ tables: ir.tables,
586
+ pgSchemaName: ir.pgSchemaName,
587
+ pgVersion: ir.pgVersion,
588
+ rlsPolicies: ir.rlsPolicies,
589
+ roles: ir.roles,
590
+ existingSchemas,
591
+ nativeEnumTypeNames: ir.nativeEnumTypeNames,
592
+ });
571
593
  }
572
594
 
573
595
  /**
@@ -602,7 +624,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
602
624
  private async introspectNamespaces(
603
625
  driver: SqlControlDriverInstance<'postgres'>,
604
626
  namespaceIds: readonly string[],
605
- ): Promise<SqlSchemaIR> {
627
+ ): Promise<PostgresSchemaIR> {
606
628
  const resolvedSchemas: string[] = [];
607
629
  for (const id of namespaceIds) {
608
630
  if (id === UNBOUND_NAMESPACE_ID) {
@@ -619,7 +641,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
619
641
  // Walk schemas sequentially: every introspectSchema call shares the one
620
642
  // control connection, so a parallel walk only serialises behind the wire
621
643
  // protocol and trips pg's "already executing a query" deprecation.
622
- const perSchema: SqlSchemaIR[] = [];
644
+ const perSchema: PostgresSchemaIR[] = [];
623
645
  for (const schema of uniqueSchemas) {
624
646
  perSchema.push(await this.introspectSchema(driver, schema));
625
647
  }
@@ -631,18 +653,23 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
631
653
  }
632
654
  }
633
655
 
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 {
656
+ const mergedRlsPolicies: PostgresRlsPolicy[] = [];
657
+ const mergedRoles: PostgresRole[] = [];
658
+ for (const ir of perSchema) {
659
+ mergedRlsPolicies.push(...ir.rlsPolicies);
660
+ mergedRoles.push(...ir.roles);
661
+ }
662
+
663
+ const first = perSchema[0];
664
+ return new PostgresSchemaIR({
640
665
  tables: mergedTables,
641
- ...ifDefined('annotations', {
642
- ...firstAnnotations,
643
- pg: { ...firstPg },
644
- }),
645
- };
666
+ pgSchemaName: first?.pgSchemaName ?? 'public',
667
+ pgVersion: first?.pgVersion ?? 'unknown',
668
+ rlsPolicies: mergedRlsPolicies,
669
+ roles: mergedRoles,
670
+ existingSchemas: first?.existingSchemas ?? ['public'],
671
+ nativeEnumTypeNames: first?.nativeEnumTypeNames ?? [],
672
+ });
646
673
  }
647
674
 
648
675
  /**
@@ -653,7 +680,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
653
680
  private async introspectSchema(
654
681
  driver: SqlControlDriverInstance<'postgres'>,
655
682
  schema: string,
656
- ): Promise<SqlSchemaIR> {
683
+ ): Promise<PostgresSchemaIR> {
657
684
  // Issue the schema-wide queries one at a time. A single control connection
658
685
  // serialises queries anyway, so Promise.all buys no parallelism here and
659
686
  // makes pg emit a "client is already executing a query" deprecation. One
@@ -1090,19 +1117,61 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1090
1117
  [schema],
1091
1118
  );
1092
1119
  const nativeEnumTypeNames = nativeEnumResult.rows.map((r) => r.typname);
1120
+ const policiesResult = await driver.query<{
1121
+ schemaname: string;
1122
+ tablename: string;
1123
+ policyname: string;
1124
+ cmd: string;
1125
+ roles: string[];
1126
+ qual: string | null;
1127
+ with_check: string | null;
1128
+ permissive: string;
1129
+ }>(
1130
+ `SELECT schemaname, tablename, policyname, cmd, roles, qual, with_check, permissive
1131
+ FROM pg_catalog.pg_policies
1132
+ WHERE schemaname = $1
1133
+ ORDER BY tablename, policyname`,
1134
+ [schema],
1135
+ );
1136
+ const rolesResult = await driver.query<{ rolname: string }>(
1137
+ `SELECT rolname
1138
+ FROM pg_catalog.pg_roles
1139
+ WHERE rolname NOT LIKE 'pg_%'
1140
+ AND rolname != 'postgres'
1141
+ ORDER BY rolname`,
1142
+ );
1143
+ const rlsPolicies: PostgresRlsPolicy[] = policiesResult.rows.map((row) => {
1144
+ const operation = mapPgCmd(row.cmd);
1145
+ const roles = [...new Set(parsePgNameArray(row.roles).map((r) => r.toLowerCase()))].sort();
1146
+ const permissive = row.permissive.toUpperCase() === 'PERMISSIVE';
1147
+ const hashSuffixMatch = /^(.+)_([0-9a-f]{8})$/.exec(row.policyname);
1148
+ const prefix = hashSuffixMatch?.[1] ?? row.policyname;
1149
+ return new PostgresRlsPolicy({
1150
+ name: row.policyname,
1151
+ prefix,
1152
+ tableName: row.tablename,
1153
+ namespaceId: row.schemaname,
1154
+ operation,
1155
+ roles,
1156
+ ...(row.qual !== null ? { using: row.qual } : {}),
1157
+ ...(row.with_check !== null ? { withCheck: row.with_check } : {}),
1158
+ permissive,
1159
+ });
1160
+ });
1093
1161
 
1094
- const annotations = {
1095
- pg: {
1096
- schema,
1097
- version: await this.getPostgresVersion(driver),
1098
- ...(nativeEnumTypeNames.length > 0 && { nativeEnumTypeNames }),
1099
- },
1100
- };
1162
+ const roles: PostgresRole[] = rolesResult.rows.map(
1163
+ (row) => new PostgresRole({ name: row.rolname, namespaceId: UNBOUND_NAMESPACE_ID }),
1164
+ );
1101
1165
 
1102
- return {
1166
+ return new PostgresSchemaIR({
1103
1167
  tables,
1104
- annotations,
1105
- };
1168
+ pgSchemaName: schema,
1169
+ pgVersion: await this.getPostgresVersion(driver),
1170
+ rlsPolicies,
1171
+ roles,
1172
+ existingSchemas: [],
1173
+ nativeEnumTypeNames,
1174
+ });
1106
1175
  }
1107
1176
 
1108
1177
  /**
@@ -1117,6 +1186,52 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1117
1186
  }
1118
1187
  }
1119
1188
 
1189
+ /**
1190
+ * Normalises a `name[]` column value from `pg_policies.roles`.
1191
+ *
1192
+ * The `pg` client's type-parser registry handles `text[]` (OID 1009) but not
1193
+ * `name[]` (OID 1003). When the parser is absent the raw Postgres text-array
1194
+ * literal (`{role1,role2}`) is returned as a string instead of a JS array.
1195
+ * This function accepts either form and returns a plain string array.
1196
+ */
1197
+ function parsePgNameArray(value: unknown): string[] {
1198
+ if (Array.isArray(value)) {
1199
+ return value as string[];
1200
+ }
1201
+ if (typeof value !== 'string') {
1202
+ return [];
1203
+ }
1204
+ const trimmed = value.trim();
1205
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
1206
+ return [];
1207
+ }
1208
+ const inner = trimmed.slice(1, -1);
1209
+ if (inner === '') {
1210
+ return [];
1211
+ }
1212
+ return inner.split(',').map((s) => s.trim());
1213
+ }
1214
+
1215
+ /**
1216
+ * Maps `pg_policies.cmd` text values to the `RlsPolicyOperation` union.
1217
+ * The `pg_policies` view renders the internal command code as an uppercase
1218
+ * English keyword; this function lowercases to match the IR type.
1219
+ */
1220
+ function mapPgCmd(cmd: string): RlsPolicyOperation {
1221
+ switch (cmd.toUpperCase()) {
1222
+ case 'SELECT':
1223
+ return 'select';
1224
+ case 'INSERT':
1225
+ return 'insert';
1226
+ case 'UPDATE':
1227
+ return 'update';
1228
+ case 'DELETE':
1229
+ return 'delete';
1230
+ default:
1231
+ return 'all';
1232
+ }
1233
+ }
1234
+
1120
1235
  /**
1121
1236
  * Extracts the namespace coordinate ids declared on a contract's storage,
1122
1237
  * or returns an empty array when no contract (or no storage / namespaces)
@@ -1532,6 +1647,37 @@ async function pgRenderAlterTable(
1532
1647
  };
1533
1648
  }
1534
1649
 
1650
+ const POLICY_OPERATION_SQL: Record<RlsPolicyOperation, string> = {
1651
+ select: 'SELECT',
1652
+ insert: 'INSERT',
1653
+ update: 'UPDATE',
1654
+ delete: 'DELETE',
1655
+ all: 'ALL',
1656
+ };
1657
+
1658
+ function pgRenderCreatePolicy(node: PostgresCreatePolicy): SqlExecuteRequest {
1659
+ const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
1660
+ const permissiveness = node.permissive ? 'PERMISSIVE' : 'RESTRICTIVE';
1661
+ const command = POLICY_OPERATION_SQL[node.operation];
1662
+ const roles = node.roles.length === 0 ? 'PUBLIC' : node.roles.join(', ');
1663
+ let sql = `CREATE POLICY ${quoteIdentifier(node.name)} ON ${tableRef} AS ${permissiveness} FOR ${command} TO ${roles}`;
1664
+ if (node.using !== undefined) {
1665
+ sql += ` USING (${node.using})`;
1666
+ }
1667
+ if (node.withCheck !== undefined) {
1668
+ sql += ` WITH CHECK (${node.withCheck})`;
1669
+ }
1670
+ return { sql, params: [] };
1671
+ }
1672
+
1673
+ function pgRenderDropPolicy(node: PostgresDropPolicy): SqlExecuteRequest {
1674
+ const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
1675
+ return {
1676
+ sql: `DROP POLICY ${quoteIdentifier(node.name)} ON ${tableRef}`,
1677
+ params: [],
1678
+ };
1679
+ }
1680
+
1535
1681
  async function pgRenderDdlExecuteRequest(
1536
1682
  ast: PostgresDdlNode,
1537
1683
  codecLookup: CodecLookup,
@@ -1540,6 +1686,8 @@ async function pgRenderDdlExecuteRequest(
1540
1686
  createTable: (node: PostgresCreateTable) => pgRenderCreateTable(node, codecLookup),
1541
1687
  createSchema: (node: PostgresCreateSchema) => Promise.resolve(pgRenderCreateSchema(node)),
1542
1688
  alterTable: (node: PostgresAlterTable) => pgRenderAlterTable(node, codecLookup),
1689
+ createPolicy: (node: PostgresCreatePolicy) => Promise.resolve(pgRenderCreatePolicy(node)),
1690
+ dropPolicy: (node: PostgresDropPolicy) => Promise.resolve(pgRenderDropPolicy(node)),
1543
1691
  };
1544
1692
  return ast.accept(visitor);
1545
1693
  }