@prisma-next/adapter-postgres 0.14.0-dev.2 → 0.14.0-dev.20
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-BvM9axNH.mjs} +4 -3
- package/dist/adapter-BvM9axNH.mjs.map +1 -0
- package/dist/adapter.d.mts.map +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/{control-adapter-Dspz5uKp.mjs → control-adapter-AffpjrYN.mjs} +165 -37
- package/dist/control-adapter-AffpjrYN.mjs.map +1 -0
- package/dist/control.d.mts +6 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +2 -2
- package/dist/{descriptor-meta-DOgMfoqm.mjs → descriptor-meta-DnWlLrI6.mjs} +3 -2
- package/dist/descriptor-meta-DnWlLrI6.mjs.map +1 -0
- package/dist/runtime.mjs +2 -2
- package/package.json +23 -22
- package/src/core/adapter.ts +1 -0
- package/src/core/control-adapter.ts +222 -41
- package/src/core/descriptor-meta.ts +1 -0
- package/src/core/sql-renderer.ts +11 -4
- package/dist/adapter-CwkcdpM_.mjs.map +0 -1
- package/dist/control-adapter-Dspz5uKp.mjs.map +0 -1
- package/dist/descriptor-meta-DOgMfoqm.mjs.map +0 -1
|
@@ -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,
|
|
@@ -45,14 +49,25 @@ import {
|
|
|
45
49
|
import type {
|
|
46
50
|
AddColumnAction,
|
|
47
51
|
AlterTableActionVisitor,
|
|
52
|
+
DropDefaultAction,
|
|
48
53
|
PostgresAlterTable,
|
|
54
|
+
PostgresCreatePolicy,
|
|
49
55
|
PostgresCreateSchema,
|
|
50
56
|
PostgresCreateTable,
|
|
51
57
|
PostgresDdlNode,
|
|
58
|
+
PostgresDropPolicy,
|
|
59
|
+
RlsPolicyOperation,
|
|
52
60
|
} from '@prisma-next/target-postgres/ddl';
|
|
53
61
|
import { parsePostgresDefault } from '@prisma-next/target-postgres/default-normalizer';
|
|
54
62
|
import { normalizeSchemaNativeType } from '@prisma-next/target-postgres/native-type-normalizer';
|
|
63
|
+
import { diffPostgresRlsPolicies } from '@prisma-next/target-postgres/planner';
|
|
55
64
|
import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
|
|
65
|
+
import {
|
|
66
|
+
isPostgresSchemaIR,
|
|
67
|
+
PostgresRlsPolicy,
|
|
68
|
+
PostgresRole,
|
|
69
|
+
PostgresSchemaIR,
|
|
70
|
+
} from '@prisma-next/target-postgres/types';
|
|
56
71
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
57
72
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
58
73
|
import { encodeControlQueryParams } from './control-codecs';
|
|
@@ -108,6 +123,18 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
108
123
|
*/
|
|
109
124
|
readonly normalizeNativeType = normalizeSchemaNativeType;
|
|
110
125
|
|
|
126
|
+
collectSchemaDiffIssues(
|
|
127
|
+
contract: Contract<SqlStorage>,
|
|
128
|
+
schema: SqlSchemaIR,
|
|
129
|
+
): readonly SchemaDiffIssue[] {
|
|
130
|
+
if (!isPostgresSchemaIR(schema)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`RLS verification requires a PostgresSchemaIR; got ${(schema as { constructor?: { name?: string } }).constructor?.name ?? typeof schema}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return diffPostgresRlsPolicies({ contract, schema });
|
|
136
|
+
}
|
|
137
|
+
|
|
111
138
|
bootstrapControlTableQueries(): readonly DdlNode[] {
|
|
112
139
|
return buildControlTableBootstrapQueries();
|
|
113
140
|
}
|
|
@@ -548,26 +575,22 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
548
575
|
driver: SqlControlDriverInstance<'postgres'>,
|
|
549
576
|
contract?: unknown,
|
|
550
577
|
schema = 'public',
|
|
551
|
-
): Promise<
|
|
578
|
+
): Promise<PostgresSchemaIR> {
|
|
552
579
|
const declaredNamespaces = extractContractNamespaceIds(contract);
|
|
553
580
|
const ir =
|
|
554
581
|
declaredNamespaces.length > 0
|
|
555
582
|
? await this.introspectNamespaces(driver, declaredNamespaces)
|
|
556
583
|
: 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
584
|
const existingSchemas = await this.listExistingSchemas(driver);
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
};
|
|
585
|
+
return new PostgresSchemaIR({
|
|
586
|
+
tables: ir.tables,
|
|
587
|
+
pgSchemaName: ir.pgSchemaName,
|
|
588
|
+
pgVersion: ir.pgVersion,
|
|
589
|
+
rlsPolicies: ir.rlsPolicies,
|
|
590
|
+
roles: ir.roles,
|
|
591
|
+
existingSchemas,
|
|
592
|
+
nativeEnumTypeNames: ir.nativeEnumTypeNames,
|
|
593
|
+
});
|
|
571
594
|
}
|
|
572
595
|
|
|
573
596
|
/**
|
|
@@ -602,7 +625,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
602
625
|
private async introspectNamespaces(
|
|
603
626
|
driver: SqlControlDriverInstance<'postgres'>,
|
|
604
627
|
namespaceIds: readonly string[],
|
|
605
|
-
): Promise<
|
|
628
|
+
): Promise<PostgresSchemaIR> {
|
|
606
629
|
const resolvedSchemas: string[] = [];
|
|
607
630
|
for (const id of namespaceIds) {
|
|
608
631
|
if (id === UNBOUND_NAMESPACE_ID) {
|
|
@@ -619,7 +642,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
619
642
|
// Walk schemas sequentially: every introspectSchema call shares the one
|
|
620
643
|
// control connection, so a parallel walk only serialises behind the wire
|
|
621
644
|
// protocol and trips pg's "already executing a query" deprecation.
|
|
622
|
-
const perSchema:
|
|
645
|
+
const perSchema: PostgresSchemaIR[] = [];
|
|
623
646
|
for (const schema of uniqueSchemas) {
|
|
624
647
|
perSchema.push(await this.introspectSchema(driver, schema));
|
|
625
648
|
}
|
|
@@ -631,18 +654,23 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
631
654
|
}
|
|
632
655
|
}
|
|
633
656
|
|
|
634
|
-
const
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
)
|
|
639
|
-
|
|
657
|
+
const mergedRlsPolicies: PostgresRlsPolicy[] = [];
|
|
658
|
+
const mergedRoles: PostgresRole[] = [];
|
|
659
|
+
for (const ir of perSchema) {
|
|
660
|
+
mergedRlsPolicies.push(...ir.rlsPolicies);
|
|
661
|
+
mergedRoles.push(...ir.roles);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const first = perSchema[0];
|
|
665
|
+
return new PostgresSchemaIR({
|
|
640
666
|
tables: mergedTables,
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
667
|
+
pgSchemaName: first?.pgSchemaName ?? 'public',
|
|
668
|
+
pgVersion: first?.pgVersion ?? 'unknown',
|
|
669
|
+
rlsPolicies: mergedRlsPolicies,
|
|
670
|
+
roles: mergedRoles,
|
|
671
|
+
existingSchemas: first?.existingSchemas ?? ['public'],
|
|
672
|
+
nativeEnumTypeNames: first?.nativeEnumTypeNames ?? [],
|
|
673
|
+
});
|
|
646
674
|
}
|
|
647
675
|
|
|
648
676
|
/**
|
|
@@ -653,7 +681,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
653
681
|
private async introspectSchema(
|
|
654
682
|
driver: SqlControlDriverInstance<'postgres'>,
|
|
655
683
|
schema: string,
|
|
656
|
-
): Promise<
|
|
684
|
+
): Promise<PostgresSchemaIR> {
|
|
657
685
|
// Issue the schema-wide queries one at a time. A single control connection
|
|
658
686
|
// serialises queries anyway, so Promise.all buys no parallelism here and
|
|
659
687
|
// makes pg emit a "client is already executing a query" deprecation. One
|
|
@@ -927,11 +955,22 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
927
955
|
nativeType = colRow.udt_name || colRow.data_type;
|
|
928
956
|
}
|
|
929
957
|
|
|
958
|
+
// Postgres reports array columns as data_type='ARRAY'; the element type
|
|
959
|
+
// is the `nativeType` string minus the trailing `[]`. Strip the suffix,
|
|
960
|
+
// normalize the element type to the canonical form (e.g. `integer` →
|
|
961
|
+
// `int4`), and record `many: true` so introspection consumers (verifier,
|
|
962
|
+
// psl-contract-infer) can reconstruct the full array type as needed.
|
|
963
|
+
const many = nativeType.endsWith('[]') ? true : undefined;
|
|
964
|
+
if (many) {
|
|
965
|
+
nativeType = normalizeSchemaNativeType(nativeType.slice(0, -2));
|
|
966
|
+
}
|
|
967
|
+
|
|
930
968
|
columns[colRow.column_name] = {
|
|
931
969
|
name: colRow.column_name,
|
|
932
970
|
nativeType,
|
|
933
971
|
nullable: colRow.is_nullable === 'YES',
|
|
934
972
|
...ifDefined('default', colRow.column_default ?? undefined),
|
|
973
|
+
...ifDefined('many', many),
|
|
935
974
|
};
|
|
936
975
|
}
|
|
937
976
|
|
|
@@ -1090,19 +1129,61 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1090
1129
|
[schema],
|
|
1091
1130
|
);
|
|
1092
1131
|
const nativeEnumTypeNames = nativeEnumResult.rows.map((r) => r.typname);
|
|
1132
|
+
const policiesResult = await driver.query<{
|
|
1133
|
+
schemaname: string;
|
|
1134
|
+
tablename: string;
|
|
1135
|
+
policyname: string;
|
|
1136
|
+
cmd: string;
|
|
1137
|
+
roles: string[];
|
|
1138
|
+
qual: string | null;
|
|
1139
|
+
with_check: string | null;
|
|
1140
|
+
permissive: string;
|
|
1141
|
+
}>(
|
|
1142
|
+
`SELECT schemaname, tablename, policyname, cmd, roles, qual, with_check, permissive
|
|
1143
|
+
FROM pg_catalog.pg_policies
|
|
1144
|
+
WHERE schemaname = $1
|
|
1145
|
+
ORDER BY tablename, policyname`,
|
|
1146
|
+
[schema],
|
|
1147
|
+
);
|
|
1148
|
+
const rolesResult = await driver.query<{ rolname: string }>(
|
|
1149
|
+
`SELECT rolname
|
|
1150
|
+
FROM pg_catalog.pg_roles
|
|
1151
|
+
WHERE rolname NOT LIKE 'pg_%'
|
|
1152
|
+
AND rolname != 'postgres'
|
|
1153
|
+
ORDER BY rolname`,
|
|
1154
|
+
);
|
|
1155
|
+
const rlsPolicies: PostgresRlsPolicy[] = policiesResult.rows.map((row) => {
|
|
1156
|
+
const operation = mapPgCmd(row.cmd);
|
|
1157
|
+
const roles = [...new Set(parsePgNameArray(row.roles).map((r) => r.toLowerCase()))].sort();
|
|
1158
|
+
const permissive = row.permissive.toUpperCase() === 'PERMISSIVE';
|
|
1159
|
+
const hashSuffixMatch = /^(.+)_([0-9a-f]{8})$/.exec(row.policyname);
|
|
1160
|
+
const prefix = hashSuffixMatch?.[1] ?? row.policyname;
|
|
1161
|
+
return new PostgresRlsPolicy({
|
|
1162
|
+
name: row.policyname,
|
|
1163
|
+
prefix,
|
|
1164
|
+
tableName: row.tablename,
|
|
1165
|
+
namespaceId: row.schemaname,
|
|
1166
|
+
operation,
|
|
1167
|
+
roles,
|
|
1168
|
+
...(row.qual !== null ? { using: row.qual } : {}),
|
|
1169
|
+
...(row.with_check !== null ? { withCheck: row.with_check } : {}),
|
|
1170
|
+
permissive,
|
|
1171
|
+
});
|
|
1172
|
+
});
|
|
1093
1173
|
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
version: await this.getPostgresVersion(driver),
|
|
1098
|
-
...(nativeEnumTypeNames.length > 0 && { nativeEnumTypeNames }),
|
|
1099
|
-
},
|
|
1100
|
-
};
|
|
1174
|
+
const roles: PostgresRole[] = rolesResult.rows.map(
|
|
1175
|
+
(row) => new PostgresRole({ name: row.rolname, namespaceId: UNBOUND_NAMESPACE_ID }),
|
|
1176
|
+
);
|
|
1101
1177
|
|
|
1102
|
-
return {
|
|
1178
|
+
return new PostgresSchemaIR({
|
|
1103
1179
|
tables,
|
|
1104
|
-
|
|
1105
|
-
|
|
1180
|
+
pgSchemaName: schema,
|
|
1181
|
+
pgVersion: await this.getPostgresVersion(driver),
|
|
1182
|
+
rlsPolicies,
|
|
1183
|
+
roles,
|
|
1184
|
+
existingSchemas: [],
|
|
1185
|
+
nativeEnumTypeNames,
|
|
1186
|
+
});
|
|
1106
1187
|
}
|
|
1107
1188
|
|
|
1108
1189
|
/**
|
|
@@ -1117,6 +1198,52 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1117
1198
|
}
|
|
1118
1199
|
}
|
|
1119
1200
|
|
|
1201
|
+
/**
|
|
1202
|
+
* Normalises a `name[]` column value from `pg_policies.roles`.
|
|
1203
|
+
*
|
|
1204
|
+
* The `pg` client's type-parser registry handles `text[]` (OID 1009) but not
|
|
1205
|
+
* `name[]` (OID 1003). When the parser is absent the raw Postgres text-array
|
|
1206
|
+
* literal (`{role1,role2}`) is returned as a string instead of a JS array.
|
|
1207
|
+
* This function accepts either form and returns a plain string array.
|
|
1208
|
+
*/
|
|
1209
|
+
function parsePgNameArray(value: unknown): string[] {
|
|
1210
|
+
if (Array.isArray(value)) {
|
|
1211
|
+
return value as string[];
|
|
1212
|
+
}
|
|
1213
|
+
if (typeof value !== 'string') {
|
|
1214
|
+
return [];
|
|
1215
|
+
}
|
|
1216
|
+
const trimmed = value.trim();
|
|
1217
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
1218
|
+
return [];
|
|
1219
|
+
}
|
|
1220
|
+
const inner = trimmed.slice(1, -1);
|
|
1221
|
+
if (inner === '') {
|
|
1222
|
+
return [];
|
|
1223
|
+
}
|
|
1224
|
+
return inner.split(',').map((s) => s.trim());
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Maps `pg_policies.cmd` text values to the `RlsPolicyOperation` union.
|
|
1229
|
+
* The `pg_policies` view renders the internal command code as an uppercase
|
|
1230
|
+
* English keyword; this function lowercases to match the IR type.
|
|
1231
|
+
*/
|
|
1232
|
+
function mapPgCmd(cmd: string): RlsPolicyOperation {
|
|
1233
|
+
switch (cmd.toUpperCase()) {
|
|
1234
|
+
case 'SELECT':
|
|
1235
|
+
return 'select';
|
|
1236
|
+
case 'INSERT':
|
|
1237
|
+
return 'insert';
|
|
1238
|
+
case 'UPDATE':
|
|
1239
|
+
return 'update';
|
|
1240
|
+
case 'DELETE':
|
|
1241
|
+
return 'delete';
|
|
1242
|
+
default:
|
|
1243
|
+
return 'all';
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1120
1247
|
/**
|
|
1121
1248
|
* Extracts the namespace coordinate ids declared on a contract's storage,
|
|
1122
1249
|
* or returns an empty array when no contract (or no storage / namespaces)
|
|
@@ -1133,6 +1260,9 @@ function extractContractNamespaceIds(contract: unknown): readonly string[] {
|
|
|
1133
1260
|
}
|
|
1134
1261
|
|
|
1135
1262
|
function normalizeFormattedType(formattedType: string, dataType: string, udtName: string): string {
|
|
1263
|
+
if (formattedType.endsWith('[]')) {
|
|
1264
|
+
return `${normalizeFormattedType(formattedType.slice(0, -2), dataType, udtName)}[]`;
|
|
1265
|
+
}
|
|
1136
1266
|
if (formattedType === 'integer') {
|
|
1137
1267
|
return 'int4';
|
|
1138
1268
|
}
|
|
@@ -1376,6 +1506,18 @@ function pgIsTextLikeNativeType(nativeType: string): boolean {
|
|
|
1376
1506
|
);
|
|
1377
1507
|
}
|
|
1378
1508
|
|
|
1509
|
+
function pgRenderArrayElement(el: unknown): string {
|
|
1510
|
+
if (el === null) return 'NULL';
|
|
1511
|
+
if (typeof el === 'number' || typeof el === 'boolean') return String(el);
|
|
1512
|
+
if (typeof el === 'string') return `'${escapeLiteral(el)}'`;
|
|
1513
|
+
return `'${escapeLiteral(JSON.stringify(el))}'`;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
function pgRenderArrayLiteral(elements: unknown[]): string {
|
|
1517
|
+
if (elements.length === 0) return "'{}'";
|
|
1518
|
+
return `ARRAY[${elements.map(pgRenderArrayElement).join(', ')}]`;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1379
1521
|
function pgInlineLiteral(wire: unknown, nativeType: string): string {
|
|
1380
1522
|
if (wire === null) return 'NULL';
|
|
1381
1523
|
if (typeof wire === 'boolean') return wire ? 'true' : 'false';
|
|
@@ -1407,6 +1549,9 @@ function pgInlineLiteral(wire: unknown, nativeType: string): string {
|
|
|
1407
1549
|
.join('');
|
|
1408
1550
|
return `'\\x${hex}'::${nativeType}`;
|
|
1409
1551
|
}
|
|
1552
|
+
if (Array.isArray(wire) && nativeType.endsWith('[]')) {
|
|
1553
|
+
return pgRenderArrayLiteral(wire);
|
|
1554
|
+
}
|
|
1410
1555
|
if (typeof wire === 'object') {
|
|
1411
1556
|
const quoted = `'${escapeLiteral(JSON.stringify(wire))}'`;
|
|
1412
1557
|
return `${quoted}::${nativeType}`;
|
|
@@ -1524,6 +1669,9 @@ async function pgRenderAlterTable(
|
|
|
1524
1669
|
const colFragment = await pgRenderDdlColumn(action.column, codecLookup);
|
|
1525
1670
|
return `ADD COLUMN ${colFragment}`;
|
|
1526
1671
|
},
|
|
1672
|
+
dropDefault(action: DropDefaultAction): Promise<string> {
|
|
1673
|
+
return Promise.resolve(`ALTER COLUMN ${quoteIdentifier(action.columnName)} DROP DEFAULT`);
|
|
1674
|
+
},
|
|
1527
1675
|
};
|
|
1528
1676
|
const actionSqls = await Promise.all(node.actions.map((a) => a.accept(actionVisitor)));
|
|
1529
1677
|
return {
|
|
@@ -1532,6 +1680,37 @@ async function pgRenderAlterTable(
|
|
|
1532
1680
|
};
|
|
1533
1681
|
}
|
|
1534
1682
|
|
|
1683
|
+
const POLICY_OPERATION_SQL: Record<RlsPolicyOperation, string> = {
|
|
1684
|
+
select: 'SELECT',
|
|
1685
|
+
insert: 'INSERT',
|
|
1686
|
+
update: 'UPDATE',
|
|
1687
|
+
delete: 'DELETE',
|
|
1688
|
+
all: 'ALL',
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1691
|
+
function pgRenderCreatePolicy(node: PostgresCreatePolicy): SqlExecuteRequest {
|
|
1692
|
+
const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
|
|
1693
|
+
const permissiveness = node.permissive ? 'PERMISSIVE' : 'RESTRICTIVE';
|
|
1694
|
+
const command = POLICY_OPERATION_SQL[node.operation];
|
|
1695
|
+
const roles = node.roles.length === 0 ? 'PUBLIC' : node.roles.join(', ');
|
|
1696
|
+
let sql = `CREATE POLICY ${quoteIdentifier(node.name)} ON ${tableRef} AS ${permissiveness} FOR ${command} TO ${roles}`;
|
|
1697
|
+
if (node.using !== undefined) {
|
|
1698
|
+
sql += ` USING (${node.using})`;
|
|
1699
|
+
}
|
|
1700
|
+
if (node.withCheck !== undefined) {
|
|
1701
|
+
sql += ` WITH CHECK (${node.withCheck})`;
|
|
1702
|
+
}
|
|
1703
|
+
return { sql, params: [] };
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
function pgRenderDropPolicy(node: PostgresDropPolicy): SqlExecuteRequest {
|
|
1707
|
+
const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
|
|
1708
|
+
return {
|
|
1709
|
+
sql: `DROP POLICY ${quoteIdentifier(node.name)} ON ${tableRef}`,
|
|
1710
|
+
params: [],
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1535
1714
|
async function pgRenderDdlExecuteRequest(
|
|
1536
1715
|
ast: PostgresDdlNode,
|
|
1537
1716
|
codecLookup: CodecLookup,
|
|
@@ -1540,6 +1719,8 @@ async function pgRenderDdlExecuteRequest(
|
|
|
1540
1719
|
createTable: (node: PostgresCreateTable) => pgRenderCreateTable(node, codecLookup),
|
|
1541
1720
|
createSchema: (node: PostgresCreateSchema) => Promise.resolve(pgRenderCreateSchema(node)),
|
|
1542
1721
|
alterTable: (node: PostgresAlterTable) => pgRenderAlterTable(node, codecLookup),
|
|
1722
|
+
createPolicy: (node: PostgresCreatePolicy) => Promise.resolve(pgRenderCreatePolicy(node)),
|
|
1723
|
+
dropPolicy: (node: PostgresDropPolicy) => Promise.resolve(pgRenderDropPolicy(node)),
|
|
1543
1724
|
};
|
|
1544
1725
|
return ast.accept(visitor);
|
|
1545
1726
|
}
|
package/src/core/sql-renderer.ts
CHANGED
|
@@ -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'
|
|
101
|
-
|
|
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 {
|
|
@@ -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"}
|