@prisma-next/adapter-postgres 0.14.0-dev.11 → 0.14.0-dev.13
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/dist/{adapter-CwkcdpM_.mjs → adapter-BtKi7wFT.mjs} +2 -2
- package/dist/{adapter-CwkcdpM_.mjs.map → adapter-BtKi7wFT.mjs.map} +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/{control-adapter-Dspz5uKp.mjs → control-adapter-DzXJMx3k.mjs} +133 -29
- package/dist/control-adapter-DzXJMx3k.mjs.map +1 -0
- package/dist/control.d.mts +6 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +1 -1
- package/dist/runtime.mjs +1 -1
- package/package.json +23 -22
- package/src/core/control-adapter.ts +189 -41
- package/dist/control-adapter-Dspz5uKp.mjs.map +0 -1
package/dist/control.d.mts
CHANGED
|
@@ -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<
|
|
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`)
|
package/dist/control.d.mts.map
CHANGED
|
@@ -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":"
|
|
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-
|
|
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-
|
|
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.
|
|
3
|
+
"version": "0.14.0-dev.13",
|
|
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.
|
|
9
|
-
"@prisma-next/contract-authoring": "0.14.0-dev.
|
|
10
|
-
"@prisma-next/errors": "0.14.0-dev.
|
|
11
|
-
"@prisma-next/family-sql": "0.14.0-dev.
|
|
12
|
-
"@prisma-next/framework-components": "0.14.0-dev.
|
|
13
|
-
"@prisma-next/ids": "0.14.0-dev.
|
|
14
|
-
"@prisma-next/sql-contract": "0.14.0-dev.
|
|
15
|
-
"@prisma-next/sql-contract-psl": "0.14.0-dev.
|
|
16
|
-
"@prisma-next/sql-contract-ts": "0.14.0-dev.
|
|
17
|
-
"@prisma-next/sql-operations": "0.14.0-dev.
|
|
18
|
-
"@prisma-next/sql-relational-core": "0.14.0-dev.
|
|
19
|
-
"@prisma-next/sql-runtime": "0.14.0-dev.
|
|
20
|
-
"@prisma-next/sql-schema-ir": "0.14.0-dev.
|
|
21
|
-
"@prisma-next/target-postgres": "0.14.0-dev.
|
|
22
|
-
"@prisma-next/utils": "0.14.0-dev.
|
|
8
|
+
"@prisma-next/contract": "0.14.0-dev.13",
|
|
9
|
+
"@prisma-next/contract-authoring": "0.14.0-dev.13",
|
|
10
|
+
"@prisma-next/errors": "0.14.0-dev.13",
|
|
11
|
+
"@prisma-next/family-sql": "0.14.0-dev.13",
|
|
12
|
+
"@prisma-next/framework-components": "0.14.0-dev.13",
|
|
13
|
+
"@prisma-next/ids": "0.14.0-dev.13",
|
|
14
|
+
"@prisma-next/sql-contract": "0.14.0-dev.13",
|
|
15
|
+
"@prisma-next/sql-contract-psl": "0.14.0-dev.13",
|
|
16
|
+
"@prisma-next/sql-contract-ts": "0.14.0-dev.13",
|
|
17
|
+
"@prisma-next/sql-operations": "0.14.0-dev.13",
|
|
18
|
+
"@prisma-next/sql-relational-core": "0.14.0-dev.13",
|
|
19
|
+
"@prisma-next/sql-runtime": "0.14.0-dev.13",
|
|
20
|
+
"@prisma-next/sql-schema-ir": "0.14.0-dev.13",
|
|
21
|
+
"@prisma-next/target-postgres": "0.14.0-dev.13",
|
|
22
|
+
"@prisma-next/utils": "0.14.0-dev.13",
|
|
23
23
|
"arktype": "^2.2.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@prisma-next/cli": "0.14.0-dev.
|
|
27
|
-
"@prisma-next/driver-postgres": "0.14.0-dev.
|
|
28
|
-
"@prisma-next/migration-tools": "0.14.0-dev.
|
|
29
|
-
"@prisma-next/
|
|
30
|
-
"@prisma-next/
|
|
31
|
-
"@prisma-next/
|
|
26
|
+
"@prisma-next/cli": "0.14.0-dev.13",
|
|
27
|
+
"@prisma-next/driver-postgres": "0.14.0-dev.13",
|
|
28
|
+
"@prisma-next/migration-tools": "0.14.0-dev.13",
|
|
29
|
+
"@prisma-next/psl-parser": "0.14.0-dev.13",
|
|
30
|
+
"@prisma-next/test-utils": "0.14.0-dev.13",
|
|
31
|
+
"@prisma-next/tsconfig": "0.14.0-dev.13",
|
|
32
|
+
"@prisma-next/tsdown": "0.14.0-dev.13",
|
|
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 {
|
|
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<
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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<
|
|
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:
|
|
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
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
)
|
|
639
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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<
|
|
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
|
|
1095
|
-
|
|
1096
|
-
|
|
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
|
-
|
|
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
|
}
|