@prisma-next/adapter-postgres 0.15.0 → 0.16.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,8 @@
1
- import type { ContractMarkerRecord, LedgerEntryRecord } from '@prisma-next/contract/types';
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
- tc.table_name,
749
- tc.constraint_name,
750
- kcu.column_name,
751
- kcu.ordinal_position
752
- FROM information_schema.table_constraints tc
753
- JOIN information_schema.key_column_usage kcu
754
- ON tc.constraint_name = kcu.constraint_name
755
- AND tc.table_schema = kcu.table_schema
756
- AND tc.table_name = kcu.table_name
757
- WHERE tc.table_schema = $1
758
- AND tc.constraint_type = 'PRIMARY KEY'
759
- ORDER BY tc.table_name, kcu.ordinal_position`,
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 actions.
763
- // Uses pg_catalog for correct positional pairing of composite FK columns
764
- // (information_schema.constraint_column_usage lacks ordinal_position,
765
- // which causes Cartesian products for multi-column FKs).
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
- tc.table_name,
779
- tc.constraint_name,
780
- kcu.column_name,
781
- kcu.ordinal_position,
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
- rc.delete_rule,
786
- rc.update_rule
787
- FROM information_schema.table_constraints tc
788
- JOIN information_schema.key_column_usage kcu
789
- ON tc.constraint_name = kcu.constraint_name
790
- AND tc.table_schema = kcu.table_schema
791
- AND tc.table_name = kcu.table_name
792
- JOIN pg_catalog.pg_constraint pgc
793
- ON pgc.conname = tc.constraint_name
794
- AND pgc.connamespace = (
795
- SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = tc.table_schema
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 = pgc.confrelid
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 = pgc.confrelid
803
- AND ref_att.attnum = pgc.confkey[kcu.ordinal_position]
804
- JOIN information_schema.referential_constraints rc
805
- ON rc.constraint_name = tc.constraint_name
806
- AND rc.constraint_schema = tc.table_schema
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
- tc.table_name,
821
- tc.constraint_name,
822
- kcu.column_name,
823
- kcu.ordinal_position
824
- FROM information_schema.table_constraints tc
825
- JOIN information_schema.key_column_usage kcu
826
- ON tc.constraint_name = kcu.constraint_name
827
- AND tc.table_schema = kcu.table_schema
828
- AND tc.table_name = kcu.table_name
829
- WHERE tc.table_schema = $1
830
- AND tc.constraint_type = 'UNIQUE'
831
- ORDER BY tc.table_name, tc.constraint_name, kcu.ordinal_position`,
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 information_schema.table_constraints tc
877
- WHERE tc.table_schema = $1
878
- AND tc.table_name = i.tablename
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.