@prisma-next/adapter-postgres 0.15.0 → 0.16.0-dev.10
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-CssyjkaR.mjs → adapter-BbIiMeqB.mjs} +2 -2
- package/dist/{adapter-CssyjkaR.mjs.map → adapter-BbIiMeqB.mjs.map} +1 -1
- package/dist/adapter.d.mts +1 -2
- package/dist/adapter.d.mts.map +1 -1
- package/dist/adapter.mjs +1 -1
- package/dist/column-types.d.mts +0 -1
- package/dist/column-types.d.mts.map +1 -1
- package/dist/{control-adapter-w2BFxQ--.mjs → control-adapter-XtIRYsO-.mjs} +161 -73
- package/dist/control-adapter-XtIRYsO-.mjs.map +1 -0
- package/dist/control.d.mts +0 -1
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +1 -1
- package/dist/operation-types.d.mts +0 -1
- package/dist/operation-types.d.mts.map +1 -1
- package/dist/runtime.d.mts +1 -2
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/{types-DkGeLfUp.d.mts → types-DyNEvnRB.d.mts} +1 -2
- package/dist/types-DyNEvnRB.d.mts.map +1 -0
- package/dist/types.d.mts +1 -1
- package/package.json +24 -24
- package/src/core/control-adapter.ts +154 -68
- package/src/core/sql-renderer.ts +79 -21
- package/dist/control-adapter-w2BFxQ--.mjs.map +0 -1
- package/dist/types-DkGeLfUp.d.mts.map +0 -1
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ColumnDefault,
|
|
3
|
+
ContractMarkerRecord,
|
|
4
|
+
LedgerEntryRecord,
|
|
5
|
+
} from '@prisma-next/contract/types';
|
|
2
6
|
import {
|
|
3
7
|
parseMarkerRowSafely,
|
|
4
8
|
rethrowMarkerReadError,
|
|
@@ -7,7 +11,7 @@ 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 SchemaNodeRef } 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';
|
|
@@ -36,6 +40,7 @@ import type {
|
|
|
36
40
|
SqlReferentialAction,
|
|
37
41
|
SqlUniqueIRInput,
|
|
38
42
|
} from '@prisma-next/sql-schema-ir/types';
|
|
43
|
+
import { RelationalSchemaNodeKind } from '@prisma-next/sql-schema-ir/types';
|
|
39
44
|
import {
|
|
40
45
|
buildControlTableBootstrapQueries,
|
|
41
46
|
buildSignMarkerBootstrapQueries,
|
|
@@ -66,6 +71,7 @@ import {
|
|
|
66
71
|
PostgresNativeEnumSchemaNode,
|
|
67
72
|
PostgresPolicySchemaNode,
|
|
68
73
|
PostgresRoleSchemaNode,
|
|
74
|
+
PostgresSchemaNodeKind,
|
|
69
75
|
PostgresTableSchemaNode,
|
|
70
76
|
} from '@prisma-next/target-postgres/types';
|
|
71
77
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
@@ -710,6 +716,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
710
716
|
numeric_scale: number | null;
|
|
711
717
|
column_default: string | null;
|
|
712
718
|
formatted_type: string | null;
|
|
719
|
+
attidentity: string;
|
|
713
720
|
}>(
|
|
714
721
|
`SELECT
|
|
715
722
|
c.table_name,
|
|
@@ -721,7 +728,8 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
721
728
|
numeric_precision,
|
|
722
729
|
numeric_scale,
|
|
723
730
|
column_default,
|
|
724
|
-
format_type(a.atttypid, a.atttypmod) AS formatted_type
|
|
731
|
+
format_type(a.atttypid, a.atttypmod) AS formatted_type,
|
|
732
|
+
a.attidentity
|
|
725
733
|
FROM information_schema.columns c
|
|
726
734
|
JOIN pg_catalog.pg_class cl
|
|
727
735
|
ON cl.relname = c.table_name
|
|
@@ -737,7 +745,13 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
737
745
|
ORDER BY c.table_name, c.ordinal_position`,
|
|
738
746
|
[schema],
|
|
739
747
|
);
|
|
740
|
-
// Query all primary keys for all tables in schema
|
|
748
|
+
// Query all primary keys for all tables in schema. Reads pg_catalog, not
|
|
749
|
+
// information_schema: `information_schema.table_constraints` hides
|
|
750
|
+
// constraints on tables where the connecting role's only privilege is
|
|
751
|
+
// SELECT (its filter grants visibility for INSERT/UPDATE/DELETE/TRUNCATE/
|
|
752
|
+
// REFERENCES/TRIGGER — not SELECT), so a SELECT-only table introspects
|
|
753
|
+
// with all its columns but zero constraints and verify reports every
|
|
754
|
+
// declared constraint as missing. pg_catalog is not privilege-filtered.
|
|
741
755
|
const pkResult = await driver.query<{
|
|
742
756
|
table_name: string;
|
|
743
757
|
constraint_name: string;
|
|
@@ -745,24 +759,29 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
745
759
|
ordinal_position: number;
|
|
746
760
|
}>(
|
|
747
761
|
`SELECT
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
FROM
|
|
753
|
-
JOIN
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
AND
|
|
759
|
-
|
|
762
|
+
cl.relname AS table_name,
|
|
763
|
+
con.conname AS constraint_name,
|
|
764
|
+
a.attname AS column_name,
|
|
765
|
+
k.ord AS ordinal_position
|
|
766
|
+
FROM pg_catalog.pg_constraint con
|
|
767
|
+
JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid
|
|
768
|
+
JOIN pg_catalog.pg_namespace ns ON ns.oid = cl.relnamespace
|
|
769
|
+
JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
|
|
770
|
+
JOIN pg_catalog.pg_attribute a
|
|
771
|
+
ON a.attrelid = con.conrelid
|
|
772
|
+
AND a.attnum = k.attnum
|
|
773
|
+
WHERE ns.nspname = $1
|
|
774
|
+
AND con.contype = 'p'
|
|
775
|
+
ORDER BY cl.relname, k.ord`,
|
|
760
776
|
[schema],
|
|
761
777
|
);
|
|
762
|
-
// Query all foreign keys for all tables in schema, including referential
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
//
|
|
778
|
+
// Query all foreign keys for all tables in schema, including referential
|
|
779
|
+
// actions. Reads pg_catalog only (see the primary-key query above for why
|
|
780
|
+
// information_schema is unusable here); `unnest(conkey) WITH ORDINALITY`
|
|
781
|
+
// pairs source and referenced columns positionally, so composite FKs
|
|
782
|
+
// round-trip in declared order. The referential-action CASEs mirror
|
|
783
|
+
// pg_constraint's confdeltype/confupdtype encoding and produce the same
|
|
784
|
+
// rule strings information_schema.referential_constraints reports.
|
|
766
785
|
const fkResult = await driver.query<{
|
|
767
786
|
table_name: string;
|
|
768
787
|
constraint_name: string;
|
|
@@ -775,41 +794,48 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
775
794
|
update_rule: string;
|
|
776
795
|
}>(
|
|
777
796
|
`SELECT
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
797
|
+
cl.relname AS table_name,
|
|
798
|
+
con.conname AS constraint_name,
|
|
799
|
+
a.attname AS column_name,
|
|
800
|
+
k.ord AS ordinal_position,
|
|
782
801
|
ref_ns.nspname AS referenced_table_schema,
|
|
783
802
|
ref_cl.relname AS referenced_table_name,
|
|
784
803
|
ref_att.attname AS referenced_column_name,
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
804
|
+
CASE con.confdeltype
|
|
805
|
+
WHEN 'a' THEN 'NO ACTION'
|
|
806
|
+
WHEN 'r' THEN 'RESTRICT'
|
|
807
|
+
WHEN 'c' THEN 'CASCADE'
|
|
808
|
+
WHEN 'n' THEN 'SET NULL'
|
|
809
|
+
WHEN 'd' THEN 'SET DEFAULT'
|
|
810
|
+
END AS delete_rule,
|
|
811
|
+
CASE con.confupdtype
|
|
812
|
+
WHEN 'a' THEN 'NO ACTION'
|
|
813
|
+
WHEN 'r' THEN 'RESTRICT'
|
|
814
|
+
WHEN 'c' THEN 'CASCADE'
|
|
815
|
+
WHEN 'n' THEN 'SET NULL'
|
|
816
|
+
WHEN 'd' THEN 'SET DEFAULT'
|
|
817
|
+
END AS update_rule
|
|
818
|
+
FROM pg_catalog.pg_constraint con
|
|
819
|
+
JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid
|
|
820
|
+
JOIN pg_catalog.pg_namespace ns ON ns.oid = cl.relnamespace
|
|
821
|
+
JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
|
|
822
|
+
JOIN pg_catalog.pg_attribute a
|
|
823
|
+
ON a.attrelid = con.conrelid
|
|
824
|
+
AND a.attnum = k.attnum
|
|
797
825
|
JOIN pg_catalog.pg_class ref_cl
|
|
798
|
-
ON ref_cl.oid =
|
|
826
|
+
ON ref_cl.oid = con.confrelid
|
|
799
827
|
JOIN pg_catalog.pg_namespace ref_ns
|
|
800
828
|
ON ref_ns.oid = ref_cl.relnamespace
|
|
801
829
|
JOIN pg_catalog.pg_attribute ref_att
|
|
802
|
-
ON ref_att.attrelid =
|
|
803
|
-
AND ref_att.attnum =
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
WHERE tc.table_schema = $1
|
|
808
|
-
AND tc.constraint_type = 'FOREIGN KEY'
|
|
809
|
-
ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`,
|
|
830
|
+
ON ref_att.attrelid = con.confrelid
|
|
831
|
+
AND ref_att.attnum = con.confkey[k.ord]
|
|
832
|
+
WHERE ns.nspname = $1
|
|
833
|
+
AND con.contype = 'f'
|
|
834
|
+
ORDER BY cl.relname, con.conname, k.ord`,
|
|
810
835
|
[schema],
|
|
811
836
|
);
|
|
812
|
-
// Query all unique constraints for all tables in schema (excluding PKs)
|
|
837
|
+
// Query all unique constraints for all tables in schema (excluding PKs).
|
|
838
|
+
// Reads pg_catalog (see the primary-key query above).
|
|
813
839
|
const uniqueResult = await driver.query<{
|
|
814
840
|
table_name: string;
|
|
815
841
|
constraint_name: string;
|
|
@@ -817,18 +843,20 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
817
843
|
ordinal_position: number;
|
|
818
844
|
}>(
|
|
819
845
|
`SELECT
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
FROM
|
|
825
|
-
JOIN
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
AND
|
|
831
|
-
|
|
846
|
+
cl.relname AS table_name,
|
|
847
|
+
con.conname AS constraint_name,
|
|
848
|
+
a.attname AS column_name,
|
|
849
|
+
k.ord AS ordinal_position
|
|
850
|
+
FROM pg_catalog.pg_constraint con
|
|
851
|
+
JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid
|
|
852
|
+
JOIN pg_catalog.pg_namespace ns ON ns.oid = cl.relnamespace
|
|
853
|
+
JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
|
|
854
|
+
JOIN pg_catalog.pg_attribute a
|
|
855
|
+
ON a.attrelid = con.conrelid
|
|
856
|
+
AND a.attnum = k.attnum
|
|
857
|
+
WHERE ns.nspname = $1
|
|
858
|
+
AND con.contype = 'u'
|
|
859
|
+
ORDER BY cl.relname, con.conname, k.ord`,
|
|
832
860
|
[schema],
|
|
833
861
|
);
|
|
834
862
|
// Query all indexes for all tables in schema (excluding constraints).
|
|
@@ -873,10 +901,9 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
873
901
|
WHERE i.schemaname = $1
|
|
874
902
|
AND NOT EXISTS (
|
|
875
903
|
SELECT 1
|
|
876
|
-
FROM
|
|
877
|
-
WHERE
|
|
878
|
-
AND
|
|
879
|
-
AND tc.constraint_name = i.indexname
|
|
904
|
+
FROM pg_catalog.pg_constraint con
|
|
905
|
+
WHERE con.conindid = ic.oid
|
|
906
|
+
AND con.contype IN ('p', 'u', 'x')
|
|
880
907
|
)
|
|
881
908
|
ORDER BY i.tablename, i.indexname, k.ord`,
|
|
882
909
|
[schema],
|
|
@@ -986,6 +1013,21 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
986
1013
|
// normalizes them itself.
|
|
987
1014
|
const resolvedNativeType = `${normalizeSchemaNativeType(nativeType)}${many ? '[]' : ''}`;
|
|
988
1015
|
const rawDefault = colRow.column_default ?? undefined;
|
|
1016
|
+
// `GENERATED ALWAYS AS IDENTITY` ('a') and `GENERATED BY DEFAULT AS
|
|
1017
|
+
// IDENTITY` ('d') both report a NULL column_default — Postgres tracks
|
|
1018
|
+
// generation via attidentity, not a default expression — so neither
|
|
1019
|
+
// variant is visible in `rawDefault` at all. The contract has no
|
|
1020
|
+
// syntax to distinguish the two, so both resolve directly to the same
|
|
1021
|
+
// `autoincrement()` a `serial` column's `nextval(...)` default
|
|
1022
|
+
// already maps to. This is the only place identity is recognized —
|
|
1023
|
+
// there is no `SqlColumnIR.identity` field; every consumer compares
|
|
1024
|
+
// `resolvedDefault` instead.
|
|
1025
|
+
const isIdentityColumn = colRow.attidentity === 'a' || colRow.attidentity === 'd';
|
|
1026
|
+
const resolvedDefault: ColumnDefault | undefined = isIdentityColumn
|
|
1027
|
+
? { kind: 'function', expression: 'autoincrement()' }
|
|
1028
|
+
: rawDefault !== undefined
|
|
1029
|
+
? parsePostgresDefault(rawDefault, resolvedNativeType)
|
|
1030
|
+
: undefined;
|
|
989
1031
|
columns[colRow.column_name] = {
|
|
990
1032
|
name: colRow.column_name,
|
|
991
1033
|
nativeType,
|
|
@@ -993,12 +1035,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
993
1035
|
...ifDefined('default', rawDefault),
|
|
994
1036
|
...ifDefined('many', many),
|
|
995
1037
|
resolvedNativeType,
|
|
996
|
-
...ifDefined(
|
|
997
|
-
'resolvedDefault',
|
|
998
|
-
rawDefault !== undefined
|
|
999
|
-
? parsePostgresDefault(rawDefault, resolvedNativeType)
|
|
1000
|
-
: undefined,
|
|
1001
|
-
),
|
|
1038
|
+
...ifDefined('resolvedDefault', resolvedDefault),
|
|
1002
1039
|
};
|
|
1003
1040
|
}
|
|
1004
1041
|
|
|
@@ -1012,6 +1049,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1012
1049
|
? {
|
|
1013
1050
|
columns: primaryKeyColumns,
|
|
1014
1051
|
...(pkRows[0]?.constraint_name ? { name: pkRows[0].constraint_name } : {}),
|
|
1052
|
+
dependsOn: postgresColumnDependsOn(schema, tableName, primaryKeyColumns),
|
|
1015
1053
|
}
|
|
1016
1054
|
: undefined;
|
|
1017
1055
|
|
|
@@ -1054,6 +1092,10 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1054
1092
|
name: fk.name,
|
|
1055
1093
|
...ifDefined('onDelete', mapReferentialAction(fk.deleteRule)),
|
|
1056
1094
|
...ifDefined('onUpdate', mapReferentialAction(fk.updateRule)),
|
|
1095
|
+
dependsOn: [
|
|
1096
|
+
postgresTableDependsOn(fk.referencedSchema, fk.referencedTable),
|
|
1097
|
+
...postgresColumnDependsOn(schema, tableName, fk.columns),
|
|
1098
|
+
],
|
|
1057
1099
|
}),
|
|
1058
1100
|
);
|
|
1059
1101
|
|
|
@@ -1078,6 +1120,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1078
1120
|
const uniques: readonly SqlUniqueIRInput[] = Array.from(uniquesMap.values()).map((uq) => ({
|
|
1079
1121
|
columns: Object.freeze([...uq.columns]) as readonly string[],
|
|
1080
1122
|
name: uq.name,
|
|
1123
|
+
dependsOn: postgresColumnDependsOn(schema, tableName, uq.columns),
|
|
1081
1124
|
}));
|
|
1082
1125
|
|
|
1083
1126
|
// Process indexes
|
|
@@ -1159,6 +1202,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1159
1202
|
unique: idx.unique,
|
|
1160
1203
|
...(idx.type !== undefined && { type: idx.type }),
|
|
1161
1204
|
...(idx.options !== undefined && { options: idx.options }),
|
|
1205
|
+
dependsOn: postgresColumnDependsOn(schema, tableName, idx.columns),
|
|
1162
1206
|
}),
|
|
1163
1207
|
);
|
|
1164
1208
|
|
|
@@ -1241,6 +1285,10 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
|
|
|
1241
1285
|
...(row.qual !== null ? { using: row.qual } : {}),
|
|
1242
1286
|
...(row.with_check !== null ? { withCheck: row.with_check } : {}),
|
|
1243
1287
|
permissive,
|
|
1288
|
+
dependsOn: [
|
|
1289
|
+
postgresTableDependsOn(row.schemaname, row.tablename),
|
|
1290
|
+
...policyRoles.map(postgresRoleDependsOn),
|
|
1291
|
+
],
|
|
1244
1292
|
});
|
|
1245
1293
|
const list = policiesByTable.get(row.tablename) ?? [];
|
|
1246
1294
|
list.push(policy);
|
|
@@ -1502,6 +1550,44 @@ function mapReferentialAction(rule: string): SqlReferentialAction | undefined {
|
|
|
1502
1550
|
return mapped;
|
|
1503
1551
|
}
|
|
1504
1552
|
|
|
1553
|
+
/**
|
|
1554
|
+
* A FK/policy dependency chain, mirroring the shape the expected-side
|
|
1555
|
+
* derivation stamps (`contractToPostgresDatabaseSchemaNode`): the database
|
|
1556
|
+
* root's fixed sentinel id, then the namespace, then the table.
|
|
1557
|
+
*/
|
|
1558
|
+
function postgresTableDependsOn(namespaceId: string, tableName: string): SchemaNodeRef {
|
|
1559
|
+
return [
|
|
1560
|
+
{ nodeKind: PostgresSchemaNodeKind.database, id: 'database' },
|
|
1561
|
+
{ nodeKind: PostgresSchemaNodeKind.namespace, id: namespaceId },
|
|
1562
|
+
{ nodeKind: PostgresSchemaNodeKind.table, id: tableName },
|
|
1563
|
+
];
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
/** A policy's dependency chain onto one of the roles it grants to. */
|
|
1567
|
+
function postgresRoleDependsOn(role: string): SchemaNodeRef {
|
|
1568
|
+
return [
|
|
1569
|
+
{ nodeKind: PostgresSchemaNodeKind.database, id: 'database' },
|
|
1570
|
+
{ nodeKind: PostgresSchemaNodeKind.role, id: role },
|
|
1571
|
+
];
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/**
|
|
1575
|
+
* The chains from a table-child object (foreign key, index, unique, primary
|
|
1576
|
+
* key) to each of the own columns it is built on — the introspection-side
|
|
1577
|
+
* mirror of `contractToPostgresDatabaseSchemaNode`'s `columnDependsOn`. An
|
|
1578
|
+
* object is dropped before the columns it covers.
|
|
1579
|
+
*/
|
|
1580
|
+
function postgresColumnDependsOn(
|
|
1581
|
+
namespaceId: string,
|
|
1582
|
+
tableName: string,
|
|
1583
|
+
columns: readonly string[],
|
|
1584
|
+
): SchemaNodeRef[] {
|
|
1585
|
+
return columns.map((column) => [
|
|
1586
|
+
...postgresTableDependsOn(namespaceId, tableName),
|
|
1587
|
+
{ nodeKind: RelationalSchemaNodeKind.column, id: `column:${column}` },
|
|
1588
|
+
]);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1505
1591
|
/**
|
|
1506
1592
|
* Groups an array of objects by a specified key.
|
|
1507
1593
|
* Returns a Map for O(1) lookup by group key.
|
package/src/core/sql-renderer.ts
CHANGED
|
@@ -5,18 +5,23 @@ import {
|
|
|
5
5
|
type AggregateExpr,
|
|
6
6
|
type AnyExpression,
|
|
7
7
|
type AnyFromSource,
|
|
8
|
+
type AnyJsonValueProjection,
|
|
8
9
|
type AnyParamRef,
|
|
9
10
|
type AnyQueryAst,
|
|
10
11
|
type BinaryExpr,
|
|
12
|
+
type CaseExpr,
|
|
13
|
+
type CastExpr,
|
|
11
14
|
type ColumnRef,
|
|
12
15
|
collectOrderedParamRefs,
|
|
13
16
|
type DeleteAst,
|
|
17
|
+
type FunctionCallExpr,
|
|
14
18
|
type InsertAst,
|
|
15
19
|
type InsertValue,
|
|
16
20
|
type JoinAst,
|
|
17
21
|
type JoinOnExpr,
|
|
18
22
|
type JsonArrayAggExpr,
|
|
19
23
|
type JsonObjectExpr,
|
|
24
|
+
type JsonValueProjectionVisitor,
|
|
20
25
|
type ListExpression,
|
|
21
26
|
LiteralExpr,
|
|
22
27
|
type LoweredParam,
|
|
@@ -121,6 +126,19 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
121
126
|
return typeof value === 'object' && value !== null;
|
|
122
127
|
}
|
|
123
128
|
|
|
129
|
+
function unreachableKind(value: never): string {
|
|
130
|
+
const candidate: unknown = value;
|
|
131
|
+
if (
|
|
132
|
+
typeof candidate === 'object' &&
|
|
133
|
+
candidate !== null &&
|
|
134
|
+
'kind' in candidate &&
|
|
135
|
+
typeof candidate.kind === 'string'
|
|
136
|
+
) {
|
|
137
|
+
return candidate.kind;
|
|
138
|
+
}
|
|
139
|
+
return 'unknown';
|
|
140
|
+
}
|
|
141
|
+
|
|
124
142
|
/**
|
|
125
143
|
* Per-render carrier threaded through every helper. Bundles the param-index map (for `$N` numbering) and the assembled-stack `codecLookup` (for cast policy at the `renderTypedParam` chokepoint). Carrying both on a single value keeps helper signatures stable.
|
|
126
144
|
*/
|
|
@@ -169,9 +187,7 @@ export function renderLoweredSql(
|
|
|
169
187
|
break;
|
|
170
188
|
// v8 ignore next 4
|
|
171
189
|
default:
|
|
172
|
-
throw new Error(
|
|
173
|
-
`Unsupported AST node kind: ${(node satisfies never as { kind: string }).kind}`,
|
|
174
|
-
);
|
|
190
|
+
throw new Error(`Unsupported AST node kind: ${unreachableKind(node)}`);
|
|
175
191
|
}
|
|
176
192
|
|
|
177
193
|
return Object.freeze({ sql, params: Object.freeze(params) });
|
|
@@ -465,13 +481,17 @@ function renderSource(
|
|
|
465
481
|
case 'function-source': {
|
|
466
482
|
const args = node.args.map((arg) => renderExpr(arg, contract, pim)).join(', ');
|
|
467
483
|
const call = `${node.fn}(${args})`;
|
|
468
|
-
|
|
484
|
+
const ordinality = node.ordinality ? ' WITH ORDINALITY' : '';
|
|
485
|
+
const alias = node.alias === undefined ? '' : ` AS ${quoteIdentifier(node.alias)}`;
|
|
486
|
+
const columnAliases =
|
|
487
|
+
node.columnAliases === undefined
|
|
488
|
+
? ''
|
|
489
|
+
: `(${node.columnAliases.map((column) => quoteIdentifier(column)).join(', ')})`;
|
|
490
|
+
return `${call}${ordinality}${alias}${columnAliases}`;
|
|
469
491
|
}
|
|
470
492
|
// v8 ignore next 4
|
|
471
493
|
default:
|
|
472
|
-
throw new Error(
|
|
473
|
-
`Unsupported source node kind: ${(node satisfies never as { kind: string }).kind}`,
|
|
474
|
-
);
|
|
494
|
+
throw new Error(`Unsupported source node kind: ${unreachableKind(node)}`);
|
|
475
495
|
}
|
|
476
496
|
}
|
|
477
497
|
|
|
@@ -518,6 +538,9 @@ function isAtomicExpressionKind(kind: AnyExpression['kind']): boolean {
|
|
|
518
538
|
case 'literal':
|
|
519
539
|
case 'aggregate':
|
|
520
540
|
case 'window-func':
|
|
541
|
+
case 'function-call':
|
|
542
|
+
case 'cast':
|
|
543
|
+
case 'case':
|
|
521
544
|
case 'json-object':
|
|
522
545
|
case 'json-array-agg':
|
|
523
546
|
case 'list':
|
|
@@ -644,6 +667,44 @@ function renderWindowFuncExpr(
|
|
|
644
667
|
return `${fn}(${args}) OVER (${over})`;
|
|
645
668
|
}
|
|
646
669
|
|
|
670
|
+
function renderFunctionCallExpr(
|
|
671
|
+
expr: FunctionCallExpr,
|
|
672
|
+
contract: PostgresContract,
|
|
673
|
+
pim: ParamIndexMap,
|
|
674
|
+
): string {
|
|
675
|
+
const args = expr.args.map((arg) => renderExpr(arg, contract, pim)).join(', ');
|
|
676
|
+
return `${expr.fn}(${args})`;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function renderCastExpr(expr: CastExpr, contract: PostgresContract, pim: ParamIndexMap): string {
|
|
680
|
+
return `CAST(${renderExpr(expr.expr, contract, pim)} AS ${expr.targetType})`;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function renderCaseExpr(expr: CaseExpr, contract: PostgresContract, pim: ParamIndexMap): string {
|
|
684
|
+
const branches = expr.branches
|
|
685
|
+
.map(
|
|
686
|
+
(branch) =>
|
|
687
|
+
`WHEN ${renderExpr(branch.condition, contract, pim)} THEN ${renderExpr(branch.value, contract, pim)}`,
|
|
688
|
+
)
|
|
689
|
+
.join(' ');
|
|
690
|
+
const elseClause =
|
|
691
|
+
expr.elseExpr === undefined ? '' : ` ELSE ${renderExpr(expr.elseExpr, contract, pim)}`;
|
|
692
|
+
return `CASE ${branches}${elseClause} END`;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function renderJsonValueProjection(
|
|
696
|
+
projection: AnyJsonValueProjection,
|
|
697
|
+
contract: PostgresContract,
|
|
698
|
+
pim: ParamIndexMap,
|
|
699
|
+
): string {
|
|
700
|
+
const visitor: JsonValueProjectionVisitor<string> = {
|
|
701
|
+
codec: ({ value }) => renderExpr(value, contract, pim),
|
|
702
|
+
native: ({ value }) => renderExpr(value, contract, pim),
|
|
703
|
+
document: ({ value }) => renderExpr(value, contract, pim),
|
|
704
|
+
};
|
|
705
|
+
return projection.accept(visitor);
|
|
706
|
+
}
|
|
707
|
+
|
|
647
708
|
function renderJsonObjectExpr(
|
|
648
709
|
expr: JsonObjectExpr,
|
|
649
710
|
contract: PostgresContract,
|
|
@@ -652,10 +713,7 @@ function renderJsonObjectExpr(
|
|
|
652
713
|
const args = expr.entries
|
|
653
714
|
.flatMap((entry): [string, string] => {
|
|
654
715
|
const key = `'${escapeLiteral(entry.key)}'`;
|
|
655
|
-
|
|
656
|
-
return [key, renderLiteral(entry.value)];
|
|
657
|
-
}
|
|
658
|
-
return [key, renderExpr(entry.value, contract, pim)];
|
|
716
|
+
return [key, renderJsonValueProjection(entry.value, contract, pim)];
|
|
659
717
|
})
|
|
660
718
|
.join(', ');
|
|
661
719
|
return `json_build_object(${args})`;
|
|
@@ -680,7 +738,7 @@ function renderJsonArrayAggExpr(
|
|
|
680
738
|
expr.orderBy && expr.orderBy.length > 0
|
|
681
739
|
? ` ORDER BY ${renderOrderByItems(expr.orderBy, contract, pim)}`
|
|
682
740
|
: '';
|
|
683
|
-
const aggregated = `json_agg(${
|
|
741
|
+
const aggregated = `json_agg(${renderJsonValueProjection(expr.expr, contract, pim)}${aggregateOrderBy})`;
|
|
684
742
|
if (expr.onEmpty === 'emptyArray') {
|
|
685
743
|
return `coalesce(${aggregated}, json_build_array())`;
|
|
686
744
|
}
|
|
@@ -702,6 +760,12 @@ function renderExpr(expr: AnyExpression, contract: PostgresContract, pim: ParamI
|
|
|
702
760
|
return renderAggregateExpr(node, contract, pim);
|
|
703
761
|
case 'window-func':
|
|
704
762
|
return renderWindowFuncExpr(node, contract, pim);
|
|
763
|
+
case 'function-call':
|
|
764
|
+
return renderFunctionCallExpr(node, contract, pim);
|
|
765
|
+
case 'cast':
|
|
766
|
+
return renderCastExpr(node, contract, pim);
|
|
767
|
+
case 'case':
|
|
768
|
+
return renderCaseExpr(node, contract, pim);
|
|
705
769
|
case 'json-object':
|
|
706
770
|
return renderJsonObjectExpr(node, contract, pim);
|
|
707
771
|
case 'json-array-agg':
|
|
@@ -738,9 +802,7 @@ function renderExpr(expr: AnyExpression, contract: PostgresContract, pim: ParamI
|
|
|
738
802
|
return renderRawExpr(node, contract, pim);
|
|
739
803
|
// v8 ignore next 4
|
|
740
804
|
default:
|
|
741
|
-
throw new Error(
|
|
742
|
-
`Unsupported expression node kind: ${(node satisfies never as { kind: string }).kind}`,
|
|
743
|
-
);
|
|
805
|
+
throw new Error(`Unsupported expression node kind: ${unreachableKind(node)}`);
|
|
744
806
|
}
|
|
745
807
|
}
|
|
746
808
|
|
|
@@ -912,9 +974,7 @@ function renderInsertValue(
|
|
|
912
974
|
return renderExpr(value, contract, pim);
|
|
913
975
|
// v8 ignore next 4
|
|
914
976
|
default:
|
|
915
|
-
throw new Error(
|
|
916
|
-
`Unsupported value node in INSERT: ${(value satisfies never as { kind: string }).kind}`,
|
|
917
|
-
);
|
|
977
|
+
throw new Error(`Unsupported value node in INSERT: ${unreachableKind(value)}`);
|
|
918
978
|
}
|
|
919
979
|
}
|
|
920
980
|
|
|
@@ -979,9 +1039,7 @@ function renderInsert(ast: InsertAst, contract: PostgresContract, pim: ParamInde
|
|
|
979
1039
|
}
|
|
980
1040
|
// v8 ignore next 4
|
|
981
1041
|
default:
|
|
982
|
-
throw new Error(
|
|
983
|
-
`Unsupported onConflict action: ${(action satisfies never as { kind: string }).kind}`,
|
|
984
|
-
);
|
|
1042
|
+
throw new Error(`Unsupported onConflict action: ${unreachableKind(action)}`);
|
|
985
1043
|
}
|
|
986
1044
|
})()
|
|
987
1045
|
: '';
|