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

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.
@@ -7,7 +7,7 @@ import {
7
7
  import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
8
8
  import { parseContractMarkerRow } from '@prisma-next/family-sql/verify';
9
9
  import type { CodecLookup, CodecRegistry } from '@prisma-next/framework-components/codec';
10
- import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
10
+ import { APP_SPACE_ID, type SchemaNodeRef } from '@prisma-next/framework-components/control';
11
11
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
12
12
  import { ledgerOriginFromStored } from '@prisma-next/migration-tools/ledger-origin';
13
13
  import { REFERENTIAL_ACTION_SQL } from '@prisma-next/sql-contract/referential-action-sql';
@@ -36,6 +36,7 @@ import type {
36
36
  SqlReferentialAction,
37
37
  SqlUniqueIRInput,
38
38
  } from '@prisma-next/sql-schema-ir/types';
39
+ import { RelationalSchemaNodeKind } from '@prisma-next/sql-schema-ir/types';
39
40
  import {
40
41
  buildControlTableBootstrapQueries,
41
42
  buildSignMarkerBootstrapQueries,
@@ -66,6 +67,7 @@ import {
66
67
  PostgresNativeEnumSchemaNode,
67
68
  PostgresPolicySchemaNode,
68
69
  PostgresRoleSchemaNode,
70
+ PostgresSchemaNodeKind,
69
71
  PostgresTableSchemaNode,
70
72
  } from '@prisma-next/target-postgres/types';
71
73
  import { blindCast } from '@prisma-next/utils/casts';
@@ -737,7 +739,13 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
737
739
  ORDER BY c.table_name, c.ordinal_position`,
738
740
  [schema],
739
741
  );
740
- // Query all primary keys for all tables in schema
742
+ // Query all primary keys for all tables in schema. Reads pg_catalog, not
743
+ // information_schema: `information_schema.table_constraints` hides
744
+ // constraints on tables where the connecting role's only privilege is
745
+ // SELECT (its filter grants visibility for INSERT/UPDATE/DELETE/TRUNCATE/
746
+ // REFERENCES/TRIGGER — not SELECT), so a SELECT-only table introspects
747
+ // with all its columns but zero constraints and verify reports every
748
+ // declared constraint as missing. pg_catalog is not privilege-filtered.
741
749
  const pkResult = await driver.query<{
742
750
  table_name: string;
743
751
  constraint_name: string;
@@ -745,24 +753,29 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
745
753
  ordinal_position: number;
746
754
  }>(
747
755
  `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`,
756
+ cl.relname AS table_name,
757
+ con.conname AS constraint_name,
758
+ a.attname AS column_name,
759
+ k.ord AS ordinal_position
760
+ FROM pg_catalog.pg_constraint con
761
+ JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid
762
+ JOIN pg_catalog.pg_namespace ns ON ns.oid = cl.relnamespace
763
+ JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
764
+ JOIN pg_catalog.pg_attribute a
765
+ ON a.attrelid = con.conrelid
766
+ AND a.attnum = k.attnum
767
+ WHERE ns.nspname = $1
768
+ AND con.contype = 'p'
769
+ ORDER BY cl.relname, k.ord`,
760
770
  [schema],
761
771
  );
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).
772
+ // Query all foreign keys for all tables in schema, including referential
773
+ // actions. Reads pg_catalog only (see the primary-key query above for why
774
+ // information_schema is unusable here); `unnest(conkey) WITH ORDINALITY`
775
+ // pairs source and referenced columns positionally, so composite FKs
776
+ // round-trip in declared order. The referential-action CASEs mirror
777
+ // pg_constraint's confdeltype/confupdtype encoding and produce the same
778
+ // rule strings information_schema.referential_constraints reports.
766
779
  const fkResult = await driver.query<{
767
780
  table_name: string;
768
781
  constraint_name: string;
@@ -775,41 +788,48 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
775
788
  update_rule: string;
776
789
  }>(
777
790
  `SELECT
778
- tc.table_name,
779
- tc.constraint_name,
780
- kcu.column_name,
781
- kcu.ordinal_position,
791
+ cl.relname AS table_name,
792
+ con.conname AS constraint_name,
793
+ a.attname AS column_name,
794
+ k.ord AS ordinal_position,
782
795
  ref_ns.nspname AS referenced_table_schema,
783
796
  ref_cl.relname AS referenced_table_name,
784
797
  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
- )
798
+ CASE con.confdeltype
799
+ WHEN 'a' THEN 'NO ACTION'
800
+ WHEN 'r' THEN 'RESTRICT'
801
+ WHEN 'c' THEN 'CASCADE'
802
+ WHEN 'n' THEN 'SET NULL'
803
+ WHEN 'd' THEN 'SET DEFAULT'
804
+ END AS delete_rule,
805
+ CASE con.confupdtype
806
+ WHEN 'a' THEN 'NO ACTION'
807
+ WHEN 'r' THEN 'RESTRICT'
808
+ WHEN 'c' THEN 'CASCADE'
809
+ WHEN 'n' THEN 'SET NULL'
810
+ WHEN 'd' THEN 'SET DEFAULT'
811
+ END AS update_rule
812
+ FROM pg_catalog.pg_constraint con
813
+ JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid
814
+ JOIN pg_catalog.pg_namespace ns ON ns.oid = cl.relnamespace
815
+ JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
816
+ JOIN pg_catalog.pg_attribute a
817
+ ON a.attrelid = con.conrelid
818
+ AND a.attnum = k.attnum
797
819
  JOIN pg_catalog.pg_class ref_cl
798
- ON ref_cl.oid = pgc.confrelid
820
+ ON ref_cl.oid = con.confrelid
799
821
  JOIN pg_catalog.pg_namespace ref_ns
800
822
  ON ref_ns.oid = ref_cl.relnamespace
801
823
  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`,
824
+ ON ref_att.attrelid = con.confrelid
825
+ AND ref_att.attnum = con.confkey[k.ord]
826
+ WHERE ns.nspname = $1
827
+ AND con.contype = 'f'
828
+ ORDER BY cl.relname, con.conname, k.ord`,
810
829
  [schema],
811
830
  );
812
- // Query all unique constraints for all tables in schema (excluding PKs)
831
+ // Query all unique constraints for all tables in schema (excluding PKs).
832
+ // Reads pg_catalog (see the primary-key query above).
813
833
  const uniqueResult = await driver.query<{
814
834
  table_name: string;
815
835
  constraint_name: string;
@@ -817,18 +837,20 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
817
837
  ordinal_position: number;
818
838
  }>(
819
839
  `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`,
840
+ cl.relname AS table_name,
841
+ con.conname AS constraint_name,
842
+ a.attname AS column_name,
843
+ k.ord AS ordinal_position
844
+ FROM pg_catalog.pg_constraint con
845
+ JOIN pg_catalog.pg_class cl ON cl.oid = con.conrelid
846
+ JOIN pg_catalog.pg_namespace ns ON ns.oid = cl.relnamespace
847
+ JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ord) ON true
848
+ JOIN pg_catalog.pg_attribute a
849
+ ON a.attrelid = con.conrelid
850
+ AND a.attnum = k.attnum
851
+ WHERE ns.nspname = $1
852
+ AND con.contype = 'u'
853
+ ORDER BY cl.relname, con.conname, k.ord`,
832
854
  [schema],
833
855
  );
834
856
  // Query all indexes for all tables in schema (excluding constraints).
@@ -873,10 +895,9 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
873
895
  WHERE i.schemaname = $1
874
896
  AND NOT EXISTS (
875
897
  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
898
+ FROM pg_catalog.pg_constraint con
899
+ WHERE con.conindid = ic.oid
900
+ AND con.contype IN ('p', 'u', 'x')
880
901
  )
881
902
  ORDER BY i.tablename, i.indexname, k.ord`,
882
903
  [schema],
@@ -1012,6 +1033,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1012
1033
  ? {
1013
1034
  columns: primaryKeyColumns,
1014
1035
  ...(pkRows[0]?.constraint_name ? { name: pkRows[0].constraint_name } : {}),
1036
+ dependsOn: postgresColumnDependsOn(schema, tableName, primaryKeyColumns),
1015
1037
  }
1016
1038
  : undefined;
1017
1039
 
@@ -1054,6 +1076,10 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1054
1076
  name: fk.name,
1055
1077
  ...ifDefined('onDelete', mapReferentialAction(fk.deleteRule)),
1056
1078
  ...ifDefined('onUpdate', mapReferentialAction(fk.updateRule)),
1079
+ dependsOn: [
1080
+ postgresTableDependsOn(fk.referencedSchema, fk.referencedTable),
1081
+ ...postgresColumnDependsOn(schema, tableName, fk.columns),
1082
+ ],
1057
1083
  }),
1058
1084
  );
1059
1085
 
@@ -1078,6 +1104,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1078
1104
  const uniques: readonly SqlUniqueIRInput[] = Array.from(uniquesMap.values()).map((uq) => ({
1079
1105
  columns: Object.freeze([...uq.columns]) as readonly string[],
1080
1106
  name: uq.name,
1107
+ dependsOn: postgresColumnDependsOn(schema, tableName, uq.columns),
1081
1108
  }));
1082
1109
 
1083
1110
  // Process indexes
@@ -1159,6 +1186,7 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1159
1186
  unique: idx.unique,
1160
1187
  ...(idx.type !== undefined && { type: idx.type }),
1161
1188
  ...(idx.options !== undefined && { options: idx.options }),
1189
+ dependsOn: postgresColumnDependsOn(schema, tableName, idx.columns),
1162
1190
  }),
1163
1191
  );
1164
1192
 
@@ -1241,6 +1269,10 @@ export class PostgresControlAdapter implements SqlControlAdapter<'postgres'> {
1241
1269
  ...(row.qual !== null ? { using: row.qual } : {}),
1242
1270
  ...(row.with_check !== null ? { withCheck: row.with_check } : {}),
1243
1271
  permissive,
1272
+ dependsOn: [
1273
+ postgresTableDependsOn(row.schemaname, row.tablename),
1274
+ ...policyRoles.map(postgresRoleDependsOn),
1275
+ ],
1244
1276
  });
1245
1277
  const list = policiesByTable.get(row.tablename) ?? [];
1246
1278
  list.push(policy);
@@ -1502,6 +1534,44 @@ function mapReferentialAction(rule: string): SqlReferentialAction | undefined {
1502
1534
  return mapped;
1503
1535
  }
1504
1536
 
1537
+ /**
1538
+ * A FK/policy dependency chain, mirroring the shape the expected-side
1539
+ * derivation stamps (`contractToPostgresDatabaseSchemaNode`): the database
1540
+ * root's fixed sentinel id, then the namespace, then the table.
1541
+ */
1542
+ function postgresTableDependsOn(namespaceId: string, tableName: string): SchemaNodeRef {
1543
+ return [
1544
+ { nodeKind: PostgresSchemaNodeKind.database, id: 'database' },
1545
+ { nodeKind: PostgresSchemaNodeKind.namespace, id: namespaceId },
1546
+ { nodeKind: PostgresSchemaNodeKind.table, id: tableName },
1547
+ ];
1548
+ }
1549
+
1550
+ /** A policy's dependency chain onto one of the roles it grants to. */
1551
+ function postgresRoleDependsOn(role: string): SchemaNodeRef {
1552
+ return [
1553
+ { nodeKind: PostgresSchemaNodeKind.database, id: 'database' },
1554
+ { nodeKind: PostgresSchemaNodeKind.role, id: role },
1555
+ ];
1556
+ }
1557
+
1558
+ /**
1559
+ * The chains from a table-child object (foreign key, index, unique, primary
1560
+ * key) to each of the own columns it is built on — the introspection-side
1561
+ * mirror of `contractToPostgresDatabaseSchemaNode`'s `columnDependsOn`. An
1562
+ * object is dropped before the columns it covers.
1563
+ */
1564
+ function postgresColumnDependsOn(
1565
+ namespaceId: string,
1566
+ tableName: string,
1567
+ columns: readonly string[],
1568
+ ): SchemaNodeRef[] {
1569
+ return columns.map((column) => [
1570
+ ...postgresTableDependsOn(namespaceId, tableName),
1571
+ { nodeKind: RelationalSchemaNodeKind.column, id: `column:${column}` },
1572
+ ]);
1573
+ }
1574
+
1505
1575
  /**
1506
1576
  * Groups an array of objects by a specified key.
1507
1577
  * Returns a Map for O(1) lookup by group key.