@prisma-next/adapter-postgres 0.14.0-dev.9 → 0.14.0-dev.90
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-CssyjkaR.mjs} +4 -3
- package/dist/adapter-CssyjkaR.mjs.map +1 -0
- package/dist/adapter.d.mts +1 -1
- package/dist/adapter.d.mts.map +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/{control-adapter-Dspz5uKp.mjs → control-adapter-w2BFxQ--.mjs} +312 -63
- package/dist/control-adapter-w2BFxQ--.mjs.map +1 -0
- package/dist/control.d.mts +23 -12
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +46 -107
- package/dist/control.mjs.map +1 -1
- package/dist/{descriptor-meta-DOgMfoqm.mjs → descriptor-meta-jMtx881n.mjs} +12 -4
- package/dist/descriptor-meta-jMtx881n.mjs.map +1 -0
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.mjs +2 -2
- package/dist/runtime.mjs.map +1 -1
- package/dist/{types-KXRwRZU8.d.mts → types-DkGeLfUp.d.mts} +3 -6
- package/dist/types-DkGeLfUp.d.mts.map +1 -0
- package/dist/types.d.mts +1 -1
- package/package.json +26 -25
- package/src/core/adapter.ts +1 -0
- package/src/core/codec-lookup.ts +2 -1
- package/src/core/control-adapter.ts +481 -92
- package/src/core/control-mutation-defaults.ts +61 -190
- package/src/core/descriptor-meta.ts +4 -0
- package/src/core/marker-ledger.ts +15 -0
- package/src/core/sql-renderer.ts +32 -6
- package/src/core/types.ts +2 -3
- package/src/exports/runtime.ts +2 -5
- 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
- package/dist/types-KXRwRZU8.d.mts.map +0 -1
|
@@ -28,15 +28,13 @@ import type {
|
|
|
28
28
|
} from '@prisma-next/sql-relational-core/ast';
|
|
29
29
|
import { isDdlNode } from '@prisma-next/sql-relational-core/ast';
|
|
30
30
|
import type {
|
|
31
|
-
|
|
31
|
+
PrimaryKeyInput,
|
|
32
32
|
SqlCheckConstraintIRInput,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
SqlColumnIRInput,
|
|
34
|
+
SqlForeignKeyIRInput,
|
|
35
|
+
SqlIndexIRInput,
|
|
36
36
|
SqlReferentialAction,
|
|
37
|
-
|
|
38
|
-
SqlTableIR,
|
|
39
|
-
SqlUniqueIR,
|
|
37
|
+
SqlUniqueIRInput,
|
|
40
38
|
} from '@prisma-next/sql-schema-ir/types';
|
|
41
39
|
import {
|
|
42
40
|
buildControlTableBootstrapQueries,
|
|
@@ -45,14 +43,31 @@ import {
|
|
|
45
43
|
import type {
|
|
46
44
|
AddColumnAction,
|
|
47
45
|
AlterTableActionVisitor,
|
|
46
|
+
DropDefaultAction,
|
|
47
|
+
PostgresAlterPolicyRename,
|
|
48
48
|
PostgresAlterTable,
|
|
49
|
+
PostgresCreatePolicy,
|
|
49
50
|
PostgresCreateSchema,
|
|
50
51
|
PostgresCreateTable,
|
|
52
|
+
PostgresCreateType,
|
|
51
53
|
PostgresDdlNode,
|
|
54
|
+
PostgresDisableRowLevelSecurity,
|
|
55
|
+
PostgresDropPolicy,
|
|
56
|
+
PostgresDropType,
|
|
57
|
+
RlsPolicyOperation,
|
|
52
58
|
} from '@prisma-next/target-postgres/ddl';
|
|
53
59
|
import { parsePostgresDefault } from '@prisma-next/target-postgres/default-normalizer';
|
|
54
60
|
import { normalizeSchemaNativeType } from '@prisma-next/target-postgres/native-type-normalizer';
|
|
61
|
+
import { parseRlsPolicyWireName } from '@prisma-next/target-postgres/rls-canonicalize';
|
|
55
62
|
import { escapeLiteral, quoteIdentifier } from '@prisma-next/target-postgres/sql-utils';
|
|
63
|
+
import {
|
|
64
|
+
PostgresDatabaseSchemaNode,
|
|
65
|
+
PostgresNamespaceSchemaNode,
|
|
66
|
+
PostgresNativeEnumSchemaNode,
|
|
67
|
+
PostgresPolicySchemaNode,
|
|
68
|
+
PostgresRoleSchemaNode,
|
|
69
|
+
PostgresTableSchemaNode,
|
|
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';
|
|
@@ -60,6 +75,7 @@ import {
|
|
|
60
75
|
execute,
|
|
61
76
|
infoSchemaTables,
|
|
62
77
|
ledger,
|
|
78
|
+
ledgerContract,
|
|
63
79
|
ledgerReadShape,
|
|
64
80
|
marker,
|
|
65
81
|
mergeInvariants,
|
|
@@ -429,7 +445,11 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
429
445
|
}
|
|
430
446
|
|
|
431
447
|
/**
|
|
432
|
-
* Appends a ledger entry for `space`.
|
|
448
|
+
* Appends a ledger entry for `space`. When the edge carries a
|
|
449
|
+
* destination contract snapshot, the content-addressed
|
|
450
|
+
* `prisma_contract.contract` store is populated first (keyed by the
|
|
451
|
+
* destination hash, DO NOTHING on revisit) so a reader never sees a
|
|
452
|
+
* ledger row whose stored destination contract is missing. See the
|
|
433
453
|
* `SqlControlAdapter.writeLedgerEntry` contract.
|
|
434
454
|
*/
|
|
435
455
|
async writeLedgerEntry(
|
|
@@ -442,10 +462,23 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
442
462
|
readonly migrationName: string;
|
|
443
463
|
readonly migrationHash: string;
|
|
444
464
|
readonly operations: readonly unknown[];
|
|
465
|
+
readonly destinationContractJson?: unknown;
|
|
445
466
|
},
|
|
446
467
|
): Promise<void> {
|
|
468
|
+
const lower = (query: AnyQueryAst) => this.lower(query, { contract: undefined });
|
|
469
|
+
if (entry.destinationContractJson !== undefined) {
|
|
470
|
+
await execute(
|
|
471
|
+
lower,
|
|
472
|
+
driver,
|
|
473
|
+
ledgerContract
|
|
474
|
+
.upsert({ core_hash: entry.to, contract_json: entry.destinationContractJson })
|
|
475
|
+
.onConflict(ledgerContract.core_hash)
|
|
476
|
+
.doNothing()
|
|
477
|
+
.build(),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
447
480
|
await execute(
|
|
448
|
-
|
|
481
|
+
lower,
|
|
449
482
|
driver,
|
|
450
483
|
ledger
|
|
451
484
|
.insert({
|
|
@@ -548,26 +581,51 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
548
581
|
driver: SqlControlDriverInstance<'postgres'>,
|
|
549
582
|
contract?: unknown,
|
|
550
583
|
schema = 'public',
|
|
551
|
-
): Promise<
|
|
584
|
+
): Promise<PostgresDatabaseSchemaNode> {
|
|
552
585
|
const declaredNamespaces = extractContractNamespaceIds(contract);
|
|
553
|
-
const
|
|
586
|
+
const resolvedSchemas =
|
|
554
587
|
declaredNamespaces.length > 0
|
|
555
|
-
? await this.
|
|
556
|
-
:
|
|
557
|
-
|
|
558
|
-
//
|
|
559
|
-
//
|
|
560
|
-
//
|
|
588
|
+
? await this.resolveNamespaceSchemas(driver, declaredNamespaces)
|
|
589
|
+
: [schema];
|
|
590
|
+
|
|
591
|
+
// Walk schemas sequentially: every introspectSchema call shares the one
|
|
592
|
+
// control connection, so a parallel walk only serialises behind the wire
|
|
593
|
+
// protocol and trips pg's "already executing a query" deprecation.
|
|
594
|
+
const namespaces: Record<string, PostgresNamespaceSchemaNode> = {};
|
|
595
|
+
let pgVersion = 'unknown';
|
|
596
|
+
for (const resolved of resolvedSchemas) {
|
|
597
|
+
const { namespace, pgVersion: version } = await this.introspectSchema(driver, resolved);
|
|
598
|
+
namespaces[resolved] = namespace;
|
|
599
|
+
pgVersion = version;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const roles = await this.introspectRoles(driver);
|
|
561
603
|
const existingSchemas = await this.listExistingSchemas(driver);
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
604
|
+
return new PostgresDatabaseSchemaNode({
|
|
605
|
+
namespaces,
|
|
606
|
+
roles,
|
|
607
|
+
existingSchemas,
|
|
608
|
+
pgVersion,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Reads cluster-scoped database roles. Roles are not schema-qualified, so
|
|
614
|
+
* this is queried once for the whole database rather than per namespace.
|
|
615
|
+
*/
|
|
616
|
+
private async introspectRoles(
|
|
617
|
+
driver: SqlControlDriverInstance<'postgres'>,
|
|
618
|
+
): Promise<readonly PostgresRoleSchemaNode[]> {
|
|
619
|
+
const rolesResult = await driver.query<{ rolname: string }>(
|
|
620
|
+
`SELECT rolname
|
|
621
|
+
FROM pg_catalog.pg_roles
|
|
622
|
+
WHERE rolname NOT LIKE 'pg_%'
|
|
623
|
+
AND rolname != 'postgres'
|
|
624
|
+
ORDER BY rolname`,
|
|
625
|
+
);
|
|
626
|
+
return rolesResult.rows.map(
|
|
627
|
+
(row) => new PostgresRoleSchemaNode({ name: row.rolname, namespaceId: UNBOUND_NAMESPACE_ID }),
|
|
628
|
+
);
|
|
571
629
|
}
|
|
572
630
|
|
|
573
631
|
/**
|
|
@@ -593,16 +651,16 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
593
651
|
}
|
|
594
652
|
|
|
595
653
|
/**
|
|
596
|
-
*
|
|
597
|
-
* the connection's `current_schema()
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
654
|
+
* Resolves the declared namespace ids to their live DDL schema names,
|
|
655
|
+
* mapping `UNBOUND_NAMESPACE_ID` to the connection's `current_schema()`
|
|
656
|
+
* and de-duplicating. The caller introspects one namespace node per
|
|
657
|
+
* resolved schema — there is no flat cross-schema merge, so two schemas
|
|
658
|
+
* holding a same-named table no longer collide.
|
|
601
659
|
*/
|
|
602
|
-
private async
|
|
660
|
+
private async resolveNamespaceSchemas(
|
|
603
661
|
driver: SqlControlDriverInstance<'postgres'>,
|
|
604
662
|
namespaceIds: readonly string[],
|
|
605
|
-
): Promise<
|
|
663
|
+
): Promise<readonly string[]> {
|
|
606
664
|
const resolvedSchemas: string[] = [];
|
|
607
665
|
for (const id of namespaceIds) {
|
|
608
666
|
if (id === UNBOUND_NAMESPACE_ID) {
|
|
@@ -614,46 +672,19 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
614
672
|
resolvedSchemas.push(id);
|
|
615
673
|
}
|
|
616
674
|
}
|
|
617
|
-
|
|
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
|
-
};
|
|
675
|
+
return Array.from(new Set(resolvedSchemas));
|
|
646
676
|
}
|
|
647
677
|
|
|
648
678
|
/**
|
|
649
|
-
* Introspects a single Postgres schema and returns
|
|
650
|
-
*
|
|
679
|
+
* Introspects a single Postgres schema and returns the namespace node for
|
|
680
|
+
* that schema (its tables, their policies, and its native enum type names),
|
|
681
|
+
* alongside the cluster-scoped Postgres version. Used by `introspect` as
|
|
651
682
|
* the per-namespace walk.
|
|
652
683
|
*/
|
|
653
684
|
private async introspectSchema(
|
|
654
685
|
driver: SqlControlDriverInstance<'postgres'>,
|
|
655
686
|
schema: string,
|
|
656
|
-
): Promise<
|
|
687
|
+
): Promise<{ readonly namespace: PostgresNamespaceSchemaNode; readonly pgVersion: string }> {
|
|
657
688
|
// Issue the schema-wide queries one at a time. A single control connection
|
|
658
689
|
// serialises queries anyway, so Promise.all buys no parallelism here and
|
|
659
690
|
// makes pg emit a "client is already executing a query" deprecation. One
|
|
@@ -895,13 +926,24 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
895
926
|
constraints.add(row.constraint_name);
|
|
896
927
|
}
|
|
897
928
|
|
|
898
|
-
const
|
|
929
|
+
const tableInputs: Record<
|
|
930
|
+
string,
|
|
931
|
+
{
|
|
932
|
+
name: string;
|
|
933
|
+
columns: Record<string, SqlColumnIRInput>;
|
|
934
|
+
primaryKey?: PrimaryKeyInput;
|
|
935
|
+
foreignKeys: readonly SqlForeignKeyIRInput[];
|
|
936
|
+
uniques: readonly SqlUniqueIRInput[];
|
|
937
|
+
indexes: readonly SqlIndexIRInput[];
|
|
938
|
+
checks?: SqlCheckConstraintIRInput[];
|
|
939
|
+
}
|
|
940
|
+
> = {};
|
|
899
941
|
|
|
900
942
|
for (const tableRow of tablesResult.rows) {
|
|
901
943
|
const tableName = tableRow.table_name;
|
|
902
944
|
|
|
903
945
|
// Process columns for this table
|
|
904
|
-
const columns: Record<string,
|
|
946
|
+
const columns: Record<string, SqlColumnIRInput> = {};
|
|
905
947
|
for (const colRow of columnsByTable.get(tableName) ?? []) {
|
|
906
948
|
let nativeType = colRow.udt_name;
|
|
907
949
|
const formattedType = colRow.formatted_type
|
|
@@ -927,11 +969,36 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
927
969
|
nativeType = colRow.udt_name || colRow.data_type;
|
|
928
970
|
}
|
|
929
971
|
|
|
972
|
+
// Postgres reports array columns as data_type='ARRAY'; the element type
|
|
973
|
+
// is the `nativeType` string minus the trailing `[]`. Strip the suffix,
|
|
974
|
+
// normalize the element type to the canonical form (e.g. `integer` →
|
|
975
|
+
// `int4`), and record `many: true` so introspection consumers (verifier,
|
|
976
|
+
// psl-contract-infer) can reconstruct the full array type as needed.
|
|
977
|
+
const many = nativeType.endsWith('[]') ? true : undefined;
|
|
978
|
+
if (many) {
|
|
979
|
+
nativeType = normalizeSchemaNativeType(nativeType.slice(0, -2));
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Resolved values comparable against the contract-derived expected
|
|
983
|
+
// side: the normalized full native type (`[]` appended for arrays)
|
|
984
|
+
// and the structured parse of the raw default. Raw fields stay
|
|
985
|
+
// untouched alongside — the relational walk still reads and
|
|
986
|
+
// normalizes them itself.
|
|
987
|
+
const resolvedNativeType = `${normalizeSchemaNativeType(nativeType)}${many ? '[]' : ''}`;
|
|
988
|
+
const rawDefault = colRow.column_default ?? undefined;
|
|
930
989
|
columns[colRow.column_name] = {
|
|
931
990
|
name: colRow.column_name,
|
|
932
991
|
nativeType,
|
|
933
992
|
nullable: colRow.is_nullable === 'YES',
|
|
934
|
-
...ifDefined('default',
|
|
993
|
+
...ifDefined('default', rawDefault),
|
|
994
|
+
...ifDefined('many', many),
|
|
995
|
+
resolvedNativeType,
|
|
996
|
+
...ifDefined(
|
|
997
|
+
'resolvedDefault',
|
|
998
|
+
rawDefault !== undefined
|
|
999
|
+
? parsePostgresDefault(rawDefault, resolvedNativeType)
|
|
1000
|
+
: undefined,
|
|
1001
|
+
),
|
|
935
1002
|
};
|
|
936
1003
|
}
|
|
937
1004
|
|
|
@@ -940,7 +1007,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
940
1007
|
const primaryKeyColumns = pkRows
|
|
941
1008
|
.sort((a, b) => a.ordinal_position - b.ordinal_position)
|
|
942
1009
|
.map((row) => row.column_name);
|
|
943
|
-
const primaryKey:
|
|
1010
|
+
const primaryKey: PrimaryKeyInput | undefined =
|
|
944
1011
|
primaryKeyColumns.length > 0
|
|
945
1012
|
? {
|
|
946
1013
|
columns: primaryKeyColumns,
|
|
@@ -978,7 +1045,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
978
1045
|
});
|
|
979
1046
|
}
|
|
980
1047
|
}
|
|
981
|
-
const foreignKeys: readonly
|
|
1048
|
+
const foreignKeys: readonly SqlForeignKeyIRInput[] = Array.from(foreignKeysMap.values()).map(
|
|
982
1049
|
(fk) => ({
|
|
983
1050
|
columns: Object.freeze([...fk.columns]) as readonly string[],
|
|
984
1051
|
referencedTable: fk.referencedTable,
|
|
@@ -1008,7 +1075,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1008
1075
|
});
|
|
1009
1076
|
}
|
|
1010
1077
|
}
|
|
1011
|
-
const uniques: readonly
|
|
1078
|
+
const uniques: readonly SqlUniqueIRInput[] = Array.from(uniquesMap.values()).map((uq) => ({
|
|
1012
1079
|
columns: Object.freeze([...uq.columns]) as readonly string[],
|
|
1013
1080
|
name: uq.name,
|
|
1014
1081
|
}));
|
|
@@ -1024,8 +1091,22 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1024
1091
|
options: Record<string, string> | undefined;
|
|
1025
1092
|
}
|
|
1026
1093
|
>();
|
|
1094
|
+
// An index with an expression key (e.g. `lower(email)`) reports that
|
|
1095
|
+
// key's row with `attname = null` (Postgres attribute numbers are
|
|
1096
|
+
// <= 0 for expressions, which the LEFT JOIN above can't resolve to a
|
|
1097
|
+
// real column). Every row for that index name is skipped below
|
|
1098
|
+
// rather than only the expression row, so the index never enters
|
|
1099
|
+
// `indexesMap` with a collapsed, misleading column list — a
|
|
1100
|
+
// two-column expression index silently reduced to its one real
|
|
1101
|
+
// column can coincide with an unrelated real single-column index,
|
|
1102
|
+
// and the schema differ's diff-tree node id is derived from the
|
|
1103
|
+
// column tuple (`sql-index/index:<columns>`), so two indexes
|
|
1104
|
+
// colliding on that tuple abort the diff with "duplicate id among
|
|
1105
|
+
// siblings" instead of a normal drift report.
|
|
1106
|
+
const indexNamesWithExpressionKey = new Set<string>();
|
|
1027
1107
|
for (const idxRow of indexesByTable.get(tableName) ?? []) {
|
|
1028
1108
|
if (!idxRow.attname) {
|
|
1109
|
+
indexNamesWithExpressionKey.add(idxRow.indexname);
|
|
1029
1110
|
continue;
|
|
1030
1111
|
}
|
|
1031
1112
|
const existing = indexesMap.get(idxRow.indexname);
|
|
@@ -1046,13 +1127,40 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1046
1127
|
});
|
|
1047
1128
|
}
|
|
1048
1129
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1130
|
+
// Two real indexes can legitimately share the exact same column tuple
|
|
1131
|
+
// on one table (e.g. a unique index and a redundant plain index) —
|
|
1132
|
+
// valid in Postgres, but the schema differ's diff-tree node id for an
|
|
1133
|
+
// index is the column tuple alone (`SqlIndexIR#id`, deliberately —
|
|
1134
|
+
// see its doc comment), so two same-tuple siblings from one
|
|
1135
|
+
// introspection abort the diff with "duplicate id among siblings"
|
|
1136
|
+
// rather than a normal drift report. Keep only one per column tuple:
|
|
1137
|
+
// the unique one when there is a unique/non-unique pair (a unique
|
|
1138
|
+
// index is a strict superset of what a plain index on the same
|
|
1139
|
+
// columns would add), otherwise the first by name for determinism.
|
|
1140
|
+
const survivingIndexes = Array.from(indexesMap.values()).filter(
|
|
1141
|
+
(idx) => !indexNamesWithExpressionKey.has(idx.name),
|
|
1142
|
+
);
|
|
1143
|
+
const bestByColumnTuple = new Map<string, (typeof survivingIndexes)[number]>();
|
|
1144
|
+
for (const idx of survivingIndexes) {
|
|
1145
|
+
const tupleKey = idx.columns.join(',');
|
|
1146
|
+
const existing = bestByColumnTuple.get(tupleKey);
|
|
1147
|
+
if (
|
|
1148
|
+
!existing ||
|
|
1149
|
+
(idx.unique && !existing.unique) ||
|
|
1150
|
+
(idx.unique === existing.unique && idx.name < existing.name)
|
|
1151
|
+
) {
|
|
1152
|
+
bestByColumnTuple.set(tupleKey, idx);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
const indexes: readonly SqlIndexIRInput[] = Array.from(bestByColumnTuple.values()).map(
|
|
1156
|
+
(idx) => ({
|
|
1157
|
+
columns: Object.freeze([...idx.columns]),
|
|
1158
|
+
name: idx.name,
|
|
1159
|
+
unique: idx.unique,
|
|
1160
|
+
...(idx.type !== undefined && { type: idx.type }),
|
|
1161
|
+
...(idx.options !== undefined && { options: idx.options }),
|
|
1162
|
+
}),
|
|
1163
|
+
);
|
|
1056
1164
|
|
|
1057
1165
|
// Process check constraints — parse each predicate into column + value set.
|
|
1058
1166
|
// Only the two shapes emitted by this slice are recognised; free-form
|
|
@@ -1069,7 +1177,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1069
1177
|
}
|
|
1070
1178
|
}
|
|
1071
1179
|
|
|
1072
|
-
|
|
1180
|
+
tableInputs[tableName] = {
|
|
1073
1181
|
name: tableName,
|
|
1074
1182
|
columns,
|
|
1075
1183
|
...ifDefined('primaryKey', primaryKey),
|
|
@@ -1080,29 +1188,110 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1080
1188
|
};
|
|
1081
1189
|
}
|
|
1082
1190
|
|
|
1083
|
-
const nativeEnumResult = await driver.query<{ typname: string }>(
|
|
1084
|
-
`SELECT t.typname
|
|
1191
|
+
const nativeEnumResult = await driver.query<{ typname: string; enumvalues: unknown }>(
|
|
1192
|
+
`SELECT t.typname, array_agg(e.enumlabel ORDER BY e.enumsortorder) AS enumvalues
|
|
1085
1193
|
FROM pg_catalog.pg_type t
|
|
1086
1194
|
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
1195
|
+
JOIN pg_catalog.pg_enum e ON e.enumtypid = t.oid
|
|
1087
1196
|
WHERE t.typtype = 'e'
|
|
1088
1197
|
AND n.nspname = $1
|
|
1198
|
+
GROUP BY t.typname
|
|
1089
1199
|
ORDER BY t.typname`,
|
|
1090
1200
|
[schema],
|
|
1091
1201
|
);
|
|
1092
|
-
const
|
|
1202
|
+
const enums = nativeEnumResult.rows.map(
|
|
1203
|
+
(r) =>
|
|
1204
|
+
new PostgresNativeEnumSchemaNode({
|
|
1205
|
+
typeName: r.typname,
|
|
1206
|
+
namespaceId: schema,
|
|
1207
|
+
members: parsePgNameArray(r.enumvalues),
|
|
1208
|
+
}),
|
|
1209
|
+
);
|
|
1210
|
+
const policiesResult = await driver.query<{
|
|
1211
|
+
schemaname: string;
|
|
1212
|
+
tablename: string;
|
|
1213
|
+
policyname: string;
|
|
1214
|
+
cmd: string;
|
|
1215
|
+
roles: string[];
|
|
1216
|
+
qual: string | null;
|
|
1217
|
+
with_check: string | null;
|
|
1218
|
+
permissive: string;
|
|
1219
|
+
}>(
|
|
1220
|
+
`SELECT schemaname, tablename, policyname, cmd, roles, qual, with_check, permissive
|
|
1221
|
+
FROM pg_catalog.pg_policies
|
|
1222
|
+
WHERE schemaname = $1
|
|
1223
|
+
ORDER BY tablename, policyname`,
|
|
1224
|
+
[schema],
|
|
1225
|
+
);
|
|
1226
|
+
const policiesByTable = new Map<string, PostgresPolicySchemaNode[]>();
|
|
1227
|
+
for (const row of policiesResult.rows) {
|
|
1228
|
+
const operation = mapPgCmd(row.cmd);
|
|
1229
|
+
const policyRoles = [
|
|
1230
|
+
...new Set(parsePgNameArray(row.roles).map((r) => r.toLowerCase())),
|
|
1231
|
+
].sort();
|
|
1232
|
+
const permissive = row.permissive.toUpperCase() === 'PERMISSIVE';
|
|
1233
|
+
const prefix = parseRlsPolicyWireName(row.policyname)?.prefix ?? row.policyname;
|
|
1234
|
+
const policy = new PostgresPolicySchemaNode({
|
|
1235
|
+
name: row.policyname,
|
|
1236
|
+
prefix,
|
|
1237
|
+
tableName: row.tablename,
|
|
1238
|
+
namespaceId: row.schemaname,
|
|
1239
|
+
operation,
|
|
1240
|
+
roles: policyRoles,
|
|
1241
|
+
...(row.qual !== null ? { using: row.qual } : {}),
|
|
1242
|
+
...(row.with_check !== null ? { withCheck: row.with_check } : {}),
|
|
1243
|
+
permissive,
|
|
1244
|
+
});
|
|
1245
|
+
const list = policiesByTable.get(row.tablename) ?? [];
|
|
1246
|
+
list.push(policy);
|
|
1247
|
+
policiesByTable.set(row.tablename, list);
|
|
1248
|
+
}
|
|
1093
1249
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1250
|
+
// RLS enablement is a table attribute (`pg_class.relrowsecurity`), not a
|
|
1251
|
+
// function of the policy set — a table can have RLS on with zero
|
|
1252
|
+
// policies (deny-all) or policies present with RLS off. relkind covers
|
|
1253
|
+
// both plain ('r') and partitioned ('p') tables: the table listing above
|
|
1254
|
+
// (`information_schema.tables`, BASE TABLE) includes partitioned parents,
|
|
1255
|
+
// and Postgres supports RLS on them.
|
|
1256
|
+
//
|
|
1257
|
+
// Kept as a SEPARATE query from the table listing on purpose. Folding
|
|
1258
|
+
// relrowsecurity into the listing would mean replacing
|
|
1259
|
+
// `information_schema.tables` (which filters by the connection role's
|
|
1260
|
+
// grants) with a raw `pg_class` scan (which does not), changing WHICH
|
|
1261
|
+
// tables the introspection returns — a real behavior shift, not a
|
|
1262
|
+
// cleanup, and one the offline golden-diff can't catch (introspection is
|
|
1263
|
+
// live-only). The only cost of two queries is the concurrent-DDL window:
|
|
1264
|
+
// a table listed but missed by this scan defaults (`?? false`) to
|
|
1265
|
+
// RLS-off. That default is fail-safe — the worst case downstream is a
|
|
1266
|
+
// spurious ENABLE (idempotent), never a spurious DISABLE.
|
|
1267
|
+
const rlsEnabledResult = await driver.query<{ tablename: string; rls_enabled: boolean }>(
|
|
1268
|
+
`SELECT c.relname AS tablename, c.relrowsecurity AS rls_enabled
|
|
1269
|
+
FROM pg_catalog.pg_class c
|
|
1270
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
1271
|
+
WHERE n.nspname = $1
|
|
1272
|
+
AND c.relkind IN ('r', 'p')
|
|
1273
|
+
ORDER BY c.relname`,
|
|
1274
|
+
[schema],
|
|
1275
|
+
);
|
|
1276
|
+
const rlsEnabledByTable = new Map<string, boolean>(
|
|
1277
|
+
rlsEnabledResult.rows.map((row) => [row.tablename, row.rls_enabled]),
|
|
1278
|
+
);
|
|
1279
|
+
|
|
1280
|
+
const tables: Record<string, PostgresTableSchemaNode> = {};
|
|
1281
|
+
for (const [tableName, input] of Object.entries(tableInputs)) {
|
|
1282
|
+
tables[tableName] = new PostgresTableSchemaNode({
|
|
1283
|
+
...input,
|
|
1284
|
+
policies: policiesByTable.get(tableName) ?? [],
|
|
1285
|
+
rlsEnabled: rlsEnabledByTable.get(tableName) ?? false,
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1101
1288
|
|
|
1102
|
-
|
|
1289
|
+
const namespace = new PostgresNamespaceSchemaNode({
|
|
1290
|
+
schemaName: schema,
|
|
1103
1291
|
tables,
|
|
1104
|
-
|
|
1105
|
-
};
|
|
1292
|
+
nativeEnums: enums,
|
|
1293
|
+
});
|
|
1294
|
+
return { namespace, pgVersion: await this.getPostgresVersion(driver) };
|
|
1106
1295
|
}
|
|
1107
1296
|
|
|
1108
1297
|
/**
|
|
@@ -1117,6 +1306,106 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1117
1306
|
}
|
|
1118
1307
|
}
|
|
1119
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* Normalises a `name[]` column value from `pg_policies.roles`.
|
|
1311
|
+
*
|
|
1312
|
+
* The `pg` client's type-parser registry handles `text[]` (OID 1009) but not
|
|
1313
|
+
* `name[]` (OID 1003). When the parser is absent the raw Postgres text-array
|
|
1314
|
+
* literal (`{role1,role2}`) is returned as a string instead of a JS array.
|
|
1315
|
+
* This function accepts either form and returns a plain string array.
|
|
1316
|
+
*
|
|
1317
|
+
* The string branch honors Postgres array-literal quoting: an element
|
|
1318
|
+
* containing a comma, quote, backslash, brace, or significant whitespace is
|
|
1319
|
+
* emitted double-quoted with `\"` / `\\` escapes, and unquoted elements are
|
|
1320
|
+
* whitespace-trimmed — so a label like `in progress` or `say "hi"` parses to
|
|
1321
|
+
* its true value instead of being split or kept escaped.
|
|
1322
|
+
*/
|
|
1323
|
+
export function parsePgNameArray(value: unknown): string[] {
|
|
1324
|
+
if (Array.isArray(value)) {
|
|
1325
|
+
return value.map(String);
|
|
1326
|
+
}
|
|
1327
|
+
if (typeof value !== 'string') {
|
|
1328
|
+
return [];
|
|
1329
|
+
}
|
|
1330
|
+
const trimmed = value.trim();
|
|
1331
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
1332
|
+
return [];
|
|
1333
|
+
}
|
|
1334
|
+
const inner = trimmed.slice(1, -1);
|
|
1335
|
+
if (inner === '') {
|
|
1336
|
+
return [];
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
const elements: string[] = [];
|
|
1340
|
+
let current = '';
|
|
1341
|
+
let inQuotes = false;
|
|
1342
|
+
let wasQuoted = false;
|
|
1343
|
+
const pushCurrent = () => {
|
|
1344
|
+
elements.push(wasQuoted ? current : current.trim());
|
|
1345
|
+
current = '';
|
|
1346
|
+
wasQuoted = false;
|
|
1347
|
+
};
|
|
1348
|
+
let i = 0;
|
|
1349
|
+
while (i < inner.length) {
|
|
1350
|
+
const char = inner.charAt(i);
|
|
1351
|
+
if (inQuotes) {
|
|
1352
|
+
if (char === '\\') {
|
|
1353
|
+
current += inner[i + 1] ?? '';
|
|
1354
|
+
i += 2;
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
if (char === '"') {
|
|
1358
|
+
inQuotes = false;
|
|
1359
|
+
i++;
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
current += char;
|
|
1363
|
+
i++;
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
if (char === '"') {
|
|
1367
|
+
inQuotes = true;
|
|
1368
|
+
wasQuoted = true;
|
|
1369
|
+
i++;
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
if (char === ',') {
|
|
1373
|
+
pushCurrent();
|
|
1374
|
+
i++;
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
current += char;
|
|
1378
|
+
i++;
|
|
1379
|
+
}
|
|
1380
|
+
// A still-open quote means the literal was malformed (e.g. `{"unterminated}`);
|
|
1381
|
+
// reject rather than emit the partial value.
|
|
1382
|
+
if (inQuotes) {
|
|
1383
|
+
return [];
|
|
1384
|
+
}
|
|
1385
|
+
pushCurrent();
|
|
1386
|
+
return elements;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* Maps `pg_policies.cmd` text values to the `RlsPolicyOperation` union.
|
|
1391
|
+
* The `pg_policies` view renders the internal command code as an uppercase
|
|
1392
|
+
* English keyword; this function lowercases to match the IR type.
|
|
1393
|
+
*/
|
|
1394
|
+
function mapPgCmd(cmd: string): RlsPolicyOperation {
|
|
1395
|
+
switch (cmd.toUpperCase()) {
|
|
1396
|
+
case 'SELECT':
|
|
1397
|
+
return 'select';
|
|
1398
|
+
case 'INSERT':
|
|
1399
|
+
return 'insert';
|
|
1400
|
+
case 'UPDATE':
|
|
1401
|
+
return 'update';
|
|
1402
|
+
case 'DELETE':
|
|
1403
|
+
return 'delete';
|
|
1404
|
+
default:
|
|
1405
|
+
return 'all';
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1120
1409
|
/**
|
|
1121
1410
|
* Extracts the namespace coordinate ids declared on a contract's storage,
|
|
1122
1411
|
* or returns an empty array when no contract (or no storage / namespaces)
|
|
@@ -1133,6 +1422,9 @@ function extractContractNamespaceIds(contract: unknown): readonly string[] {
|
|
|
1133
1422
|
}
|
|
1134
1423
|
|
|
1135
1424
|
function normalizeFormattedType(formattedType: string, dataType: string, udtName: string): string {
|
|
1425
|
+
if (formattedType.endsWith('[]')) {
|
|
1426
|
+
return `${normalizeFormattedType(formattedType.slice(0, -2), dataType, udtName)}[]`;
|
|
1427
|
+
}
|
|
1136
1428
|
if (formattedType === 'integer') {
|
|
1137
1429
|
return 'int4';
|
|
1138
1430
|
}
|
|
@@ -1376,6 +1668,18 @@ function pgIsTextLikeNativeType(nativeType: string): boolean {
|
|
|
1376
1668
|
);
|
|
1377
1669
|
}
|
|
1378
1670
|
|
|
1671
|
+
function pgRenderArrayElement(el: unknown): string {
|
|
1672
|
+
if (el === null) return 'NULL';
|
|
1673
|
+
if (typeof el === 'number' || typeof el === 'boolean') return String(el);
|
|
1674
|
+
if (typeof el === 'string') return `'${escapeLiteral(el)}'`;
|
|
1675
|
+
return `'${escapeLiteral(JSON.stringify(el))}'`;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
function pgRenderArrayLiteral(elements: unknown[]): string {
|
|
1679
|
+
if (elements.length === 0) return "'{}'";
|
|
1680
|
+
return `ARRAY[${elements.map(pgRenderArrayElement).join(', ')}]`;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1379
1683
|
function pgInlineLiteral(wire: unknown, nativeType: string): string {
|
|
1380
1684
|
if (wire === null) return 'NULL';
|
|
1381
1685
|
if (typeof wire === 'boolean') return wire ? 'true' : 'false';
|
|
@@ -1407,6 +1711,9 @@ function pgInlineLiteral(wire: unknown, nativeType: string): string {
|
|
|
1407
1711
|
.join('');
|
|
1408
1712
|
return `'\\x${hex}'::${nativeType}`;
|
|
1409
1713
|
}
|
|
1714
|
+
if (Array.isArray(wire) && nativeType.endsWith('[]')) {
|
|
1715
|
+
return pgRenderArrayLiteral(wire);
|
|
1716
|
+
}
|
|
1410
1717
|
if (typeof wire === 'object') {
|
|
1411
1718
|
const quoted = `'${escapeLiteral(JSON.stringify(wire))}'`;
|
|
1412
1719
|
return `${quoted}::${nativeType}`;
|
|
@@ -1477,6 +1784,9 @@ function pgRenderDdlConstraint(constraint: DdlTableConstraint): string {
|
|
|
1477
1784
|
}
|
|
1478
1785
|
return sql;
|
|
1479
1786
|
}
|
|
1787
|
+
if (constraint.kind === 'check-expression') {
|
|
1788
|
+
return `CONSTRAINT ${quoteIdentifier(constraint.name)} CHECK (${constraint.expression})`;
|
|
1789
|
+
}
|
|
1480
1790
|
const cols = constraint.columns.map(quoteIdentifier).join(', ');
|
|
1481
1791
|
if (constraint.name !== undefined) {
|
|
1482
1792
|
return `CONSTRAINT ${quoteIdentifier(constraint.name)} UNIQUE (${cols})`;
|
|
@@ -1512,6 +1822,27 @@ function pgRenderCreateSchema(node: PostgresCreateSchema): SqlExecuteRequest {
|
|
|
1512
1822
|
};
|
|
1513
1823
|
}
|
|
1514
1824
|
|
|
1825
|
+
function pgRenderCreateType(node: PostgresCreateType): SqlExecuteRequest {
|
|
1826
|
+
const typeRef = node.schema
|
|
1827
|
+
? `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.name)}`
|
|
1828
|
+
: quoteIdentifier(node.name);
|
|
1829
|
+
const values = node.values.map((value) => `'${escapeLiteral(value)}'`).join(', ');
|
|
1830
|
+
return {
|
|
1831
|
+
sql: `CREATE TYPE ${typeRef} AS ENUM (${values})`,
|
|
1832
|
+
params: [],
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
function pgRenderDropType(node: PostgresDropType): SqlExecuteRequest {
|
|
1837
|
+
const typeRef = node.schema
|
|
1838
|
+
? `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.name)}`
|
|
1839
|
+
: quoteIdentifier(node.name);
|
|
1840
|
+
return {
|
|
1841
|
+
sql: `DROP TYPE ${typeRef}`,
|
|
1842
|
+
params: [],
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1515
1846
|
async function pgRenderAlterTable(
|
|
1516
1847
|
node: PostgresAlterTable,
|
|
1517
1848
|
codecLookup: CodecLookup,
|
|
@@ -1524,6 +1855,9 @@ async function pgRenderAlterTable(
|
|
|
1524
1855
|
const colFragment = await pgRenderDdlColumn(action.column, codecLookup);
|
|
1525
1856
|
return `ADD COLUMN ${colFragment}`;
|
|
1526
1857
|
},
|
|
1858
|
+
dropDefault(action: DropDefaultAction): Promise<string> {
|
|
1859
|
+
return Promise.resolve(`ALTER COLUMN ${quoteIdentifier(action.columnName)} DROP DEFAULT`);
|
|
1860
|
+
},
|
|
1527
1861
|
};
|
|
1528
1862
|
const actionSqls = await Promise.all(node.actions.map((a) => a.accept(actionVisitor)));
|
|
1529
1863
|
return {
|
|
@@ -1532,6 +1866,53 @@ async function pgRenderAlterTable(
|
|
|
1532
1866
|
};
|
|
1533
1867
|
}
|
|
1534
1868
|
|
|
1869
|
+
const POLICY_OPERATION_SQL: Record<RlsPolicyOperation, string> = {
|
|
1870
|
+
select: 'SELECT',
|
|
1871
|
+
insert: 'INSERT',
|
|
1872
|
+
update: 'UPDATE',
|
|
1873
|
+
delete: 'DELETE',
|
|
1874
|
+
all: 'ALL',
|
|
1875
|
+
};
|
|
1876
|
+
|
|
1877
|
+
function pgRenderCreatePolicy(node: PostgresCreatePolicy): SqlExecuteRequest {
|
|
1878
|
+
const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
|
|
1879
|
+
const permissiveness = node.permissive ? 'PERMISSIVE' : 'RESTRICTIVE';
|
|
1880
|
+
const command = POLICY_OPERATION_SQL[node.operation];
|
|
1881
|
+
const roles = node.roles.length === 0 ? 'PUBLIC' : node.roles.join(', ');
|
|
1882
|
+
let sql = `CREATE POLICY ${quoteIdentifier(node.name)} ON ${tableRef} AS ${permissiveness} FOR ${command} TO ${roles}`;
|
|
1883
|
+
if (node.using !== undefined) {
|
|
1884
|
+
sql += ` USING (${node.using})`;
|
|
1885
|
+
}
|
|
1886
|
+
if (node.withCheck !== undefined) {
|
|
1887
|
+
sql += ` WITH CHECK (${node.withCheck})`;
|
|
1888
|
+
}
|
|
1889
|
+
return { sql, params: [] };
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
function pgRenderDropPolicy(node: PostgresDropPolicy): SqlExecuteRequest {
|
|
1893
|
+
const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
|
|
1894
|
+
return {
|
|
1895
|
+
sql: `DROP POLICY ${quoteIdentifier(node.name)} ON ${tableRef}`,
|
|
1896
|
+
params: [],
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
function pgRenderAlterPolicyRename(node: PostgresAlterPolicyRename): SqlExecuteRequest {
|
|
1901
|
+
const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
|
|
1902
|
+
return {
|
|
1903
|
+
sql: `ALTER POLICY ${quoteIdentifier(node.name)} ON ${tableRef} RENAME TO ${quoteIdentifier(node.newName)}`,
|
|
1904
|
+
params: [],
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
function pgRenderDisableRowLevelSecurity(node: PostgresDisableRowLevelSecurity): SqlExecuteRequest {
|
|
1909
|
+
const tableRef = `${quoteIdentifier(node.schema)}.${quoteIdentifier(node.table)}`;
|
|
1910
|
+
return {
|
|
1911
|
+
sql: `ALTER TABLE ${tableRef} DISABLE ROW LEVEL SECURITY`,
|
|
1912
|
+
params: [],
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1535
1916
|
async function pgRenderDdlExecuteRequest(
|
|
1536
1917
|
ast: PostgresDdlNode,
|
|
1537
1918
|
codecLookup: CodecLookup,
|
|
@@ -1539,7 +1920,15 @@ async function pgRenderDdlExecuteRequest(
|
|
|
1539
1920
|
const visitor = {
|
|
1540
1921
|
createTable: (node: PostgresCreateTable) => pgRenderCreateTable(node, codecLookup),
|
|
1541
1922
|
createSchema: (node: PostgresCreateSchema) => Promise.resolve(pgRenderCreateSchema(node)),
|
|
1923
|
+
createType: (node: PostgresCreateType) => Promise.resolve(pgRenderCreateType(node)),
|
|
1924
|
+
dropType: (node: PostgresDropType) => Promise.resolve(pgRenderDropType(node)),
|
|
1542
1925
|
alterTable: (node: PostgresAlterTable) => pgRenderAlterTable(node, codecLookup),
|
|
1926
|
+
createPolicy: (node: PostgresCreatePolicy) => Promise.resolve(pgRenderCreatePolicy(node)),
|
|
1927
|
+
dropPolicy: (node: PostgresDropPolicy) => Promise.resolve(pgRenderDropPolicy(node)),
|
|
1928
|
+
alterPolicyRename: (node: PostgresAlterPolicyRename) =>
|
|
1929
|
+
Promise.resolve(pgRenderAlterPolicyRename(node)),
|
|
1930
|
+
disableRowLevelSecurity: (node: PostgresDisableRowLevelSecurity) =>
|
|
1931
|
+
Promise.resolve(pgRenderDisableRowLevelSecurity(node)),
|
|
1543
1932
|
};
|
|
1544
1933
|
return ast.accept(visitor);
|
|
1545
1934
|
}
|