When \d a table referenced by a foreign key on a partitioned table, you
currently get this:

             Table "public.referenced"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 a      | integer |           | not null | 
Indexes:
    "referenced_pkey" PRIMARY KEY, btree (a)
Referenced by:
    TABLE "hashp96_39" CONSTRAINT "hashp_a_fkey" FOREIGN KEY (a) REFERENCES 
referenced(a)
    TABLE "hashp96_38" CONSTRAINT "hashp_a_fkey" FOREIGN KEY (a) REFERENCES 
referenced(a)
    TABLE "hashp96_37" CONSTRAINT "hashp_a_fkey" FOREIGN KEY (a) REFERENCES 
referenced(a)
    TABLE "hashp96_36" CONSTRAINT "hashp_a_fkey" FOREIGN KEY (a) REFERENCES 
referenced(a)
   (thousands more)

This is not very useful.  I propose that we change it so that it only
displays the one on the partitioned table on which the constraint was
defined:
             Table "public.referenced"
 Column │  Type   │ Collation │ Nullable │ Default 
────────┼─────────┼───────────┼──────────┼─────────
 a      │ integer │           │ not null │ 
Indexes:
    "referenced_pkey" PRIMARY KEY, btree (a)
Referenced by:
    TABLE "hashp" CONSTRAINT "hashp_a_fkey" FOREIGN KEY (a) REFERENCES 
referenced(a)
    TABLE "hashp" CONSTRAINT "hashp_b_fkey" FOREIGN KEY (b) REFERENCES 
referenced(a)
    TABLE "parted" CONSTRAINT "parted_a_fkey" FOREIGN KEY (a) REFERENCES 
referenced(a)

Which results in the actually useful info.

Also, when describing one of the partitions, I propose we add a "TABLE
foo" prefix to the constraint line, so that it indicates on which
ancestor table the constraint was defined.  So instead of this:

\d parted1
              Table "public.parted1"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 a      | integer |           | not null | 
Partition of: parted FOR VALUES FROM (0) TO (1)
Foreign-key constraints:
    "parted_a_fkey" FOREIGN KEY (a) REFERENCES referenced(a)

we get this:

\d parted1
              Table "public.parted1"
 Column │  Type   │ Collation │ Nullable │ Default 
────────┼─────────┼───────────┼──────────┼─────────
 a      │ integer │           │ not null │ 
Partition of: parted FOR VALUES FROM (0) TO (1)
Foreign-key constraints:
    TABLE "parted" CONSTRAINT "parted_a_fkey" FOREIGN KEY (a) REFERENCES 
referenced(a)

In some cases (such as in the regression tests that change in this
commit) the constraint name is different in the parent than the
partition, and it is more useful to display the parent's constraint name
rather than the partition's.


My first instinct is to change this in psql for Postgres 11, unless
there's much opposition to that.

Patch attached.


PS -- it surprises me that we've got this far without an index on
pg_constraint.confrelid.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>From fcc42e59fde284024dd5b3793ee503b0b515f51e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvhe...@alvh.no-ip.org>
Date: Tue, 4 Dec 2018 11:04:28 -0300
Subject: [PATCH] Fix psql display of FKs on partitioned tables

... as well as the display of partitioned tables as referencing, when
the referenced table is described.

With this change, instead of listing every single partition of the other
side, we only list the parent partitioned table on which the constraint
was explicitly declared.

We also add a "TABLE foo" prefix when describing the foreign key in a
partition, indicating which ancestor table the constraint was declared
in.
---
 src/bin/psql/describe.c                   | 61 +++++++++++++++++++++++--------
 src/test/regress/expected/foreign_key.out | 16 ++++----
 2 files changed, 53 insertions(+), 24 deletions(-)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4ca0db1d0c..a84fa8cb7b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2358,17 +2358,28 @@ describeOneTableDetails(const char *schemaname,
 		/*
 		 * Print foreign-key constraints (there are none if no triggers,
 		 * except if the table is partitioned, in which case the triggers
-		 * appear in the partitions)
+		 * appear in the partitions).  Note we put the constraints defined
+		 * in this table first, followed by the constraints defined in
+		 * ancestor partitioned tables. XXX maybe should display in hierarchy
+		 * order.
 		 */
 		if (tableinfo.hastriggers ||
 			tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			printfPQExpBuffer(&buf,
-							  "SELECT conname,\n"
-							  "  pg_catalog.pg_get_constraintdef(r.oid, true) as condef\n"
-							  "FROM pg_catalog.pg_constraint r\n"
-							  "WHERE r.conrelid = '%s' AND r.contype = 'f' ORDER BY 1;",
-							  oid);
+							  "WITH RECURSIVE constraints (oid, parent, conname, conrelid) as (\n"
+							  "   SELECT oid, conparentid, conname, conrelid\n"
+							  "     FROM pg_catalog.pg_constraint WHERE contype = 'f' AND conrelid = '%s'\n"
+							  "  UNION\n"
+							  "   SELECT pc.oid, pc.conparentid, pc.conname, pc.conrelid\n"
+							  "     FROM constraints, pg_constraint pc\n"
+							  "    WHERE pc.oid = constraints.parent\n"
+							  ") SELECT conrelid = '%s' as conislocal, conname,\n"
+							  "         pg_catalog.pg_get_constraintdef(oid), conrelid::pg_catalog.regclass\n"
+							  "    FROM constraints\n"
+							  "   WHERE parent = 0\n"
+							  "ORDER BY conislocal DESC, conname;",
+							  oid, oid);
 			result = PSQLexec(buf.data);
 			if (!result)
 				goto error_return;
@@ -2380,10 +2391,20 @@ describeOneTableDetails(const char *schemaname,
 				printTableAddFooter(&cont, _("Foreign-key constraints:"));
 				for (i = 0; i < tuples; i++)
 				{
-					/* untranslated constraint name and def */
-					printfPQExpBuffer(&buf, "    \"%s\" %s",
-									  PQgetvalue(result, i, 0),
-									  PQgetvalue(result, i, 1));
+					/*
+					 * Print untranslated constraint name and definition.
+					 * Use a "TABLE tab" prefix when the constraint is
+					 * defined in a parent partitioned table.
+					 */
+					if (strcmp(PQgetvalue(result, i, 0), "f") == 0)
+						printfPQExpBuffer(&buf, "    TABLE \"%s\" CONSTRAINT \"%s\" %s",
+										  PQgetvalue(result, i, 3),
+										  PQgetvalue(result, i, 1),
+										  PQgetvalue(result, i, 2));
+					else
+						printfPQExpBuffer(&buf, "    \"%s\" %s",
+										  PQgetvalue(result, i, 1),
+										  PQgetvalue(result, i, 2));
 
 					printTableAddFooter(&cont, buf.data);
 				}
@@ -2392,13 +2413,21 @@ describeOneTableDetails(const char *schemaname,
 		}
 
 		/* print incoming foreign-key references (none if no triggers) */
-		if (tableinfo.hastriggers)
+		if (tableinfo.hastriggers ||
+			tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
 		{
 			printfPQExpBuffer(&buf,
-							  "SELECT conname, conrelid::pg_catalog.regclass,\n"
-							  "  pg_catalog.pg_get_constraintdef(c.oid, true) as condef\n"
-							  "FROM pg_catalog.pg_constraint c\n"
-							  "WHERE c.confrelid = '%s' AND c.contype = 'f' ORDER BY 1;",
+							  "WITH RECURSIVE constraints (oid, parent, conname, conrelid, confrelid) as (\n"
+							  "   SELECT oid, conparentid, conname, conrelid, confrelid\n"
+							  "     FROM pg_catalog.pg_constraint WHERE contype = 'f' AND confrelid = '%s'\n"
+							  "  UNION\n"
+							  "   SELECT pc.oid, pc.conparentid, pc.conname, pc.conrelid, pc.confrelid\n"
+							  "     FROM constraints, pg_constraint pc\n"
+							  "    WHERE pc.oid = constraints.parent\n"
+							  ") SELECT conrelid::pg_catalog.regclass, conname, pg_catalog.pg_get_constraintdef(oid)\n"
+							  "    FROM constraints\n"
+							  "   WHERE parent = 0\n"
+							  "ORDER BY conname;",
 							  oid);
 			result = PSQLexec(buf.data);
 			if (!result)
@@ -2412,8 +2441,8 @@ describeOneTableDetails(const char *schemaname,
 				for (i = 0; i < tuples; i++)
 				{
 					printfPQExpBuffer(&buf, "    TABLE \"%s\" CONSTRAINT \"%s\" %s",
-									  PQgetvalue(result, i, 1),
 									  PQgetvalue(result, i, 0),
+									  PQgetvalue(result, i, 1),
 									  PQgetvalue(result, i, 2));
 
 					printTableAddFooter(&cont, buf.data);
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index 52164e89d2..c1feb76f61 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -1680,7 +1680,7 @@ ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN
  a      | integer |           |          | 
 Partition of: fk_partitioned_fk FOR VALUES IN (1500, 1502)
 Foreign-key constraints:
-    "fk_partitioned_fk_2_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+    TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
 
 DROP TABLE fk_partitioned_fk_2;
 CREATE TABLE fk_partitioned_fk_4 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE) PARTITION BY RANGE (b, a);
@@ -1700,7 +1700,7 @@ ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_4 FOR VALUES IN
 Partition of: fk_partitioned_fk FOR VALUES IN (3500, 3502)
 Partition key: RANGE (b, a)
 Foreign-key constraints:
-    "fk_partitioned_fk_4_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+    TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
 Number of partitions: 2 (Use \d+ to list them.)
 
 \d fk_partitioned_fk_4_1
@@ -1711,7 +1711,7 @@ Number of partitions: 2 (Use \d+ to list them.)
  b      | integer |           |          | 
 Partition of: fk_partitioned_fk_4 FOR VALUES FROM (1, 1) TO (100, 100)
 Foreign-key constraints:
-    "fk_partitioned_fk_4_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+    TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
 
 -- this one has an FK with mismatched properties
 \d fk_partitioned_fk_4_2
@@ -1723,7 +1723,7 @@ Foreign-key constraints:
 Partition of: fk_partitioned_fk_4 FOR VALUES FROM (100, 100) TO (1000, 1000)
 Foreign-key constraints:
     "fk_partitioned_fk_4_2_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE SET NULL
-    "fk_partitioned_fk_4_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+    TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
 
 CREATE TABLE fk_partitioned_fk_5 (a int, b int,
 	FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE,
@@ -1747,7 +1747,7 @@ Partition key: RANGE (a)
 Foreign-key constraints:
     "fk_partitioned_fk_5_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE
     "fk_partitioned_fk_5_a_fkey1" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
-    "fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+    TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
 Number of partitions: 1 (Use \d+ to list them.)
 
 -- verify that it works to reattaching a child with multiple candidate
@@ -1763,9 +1763,9 @@ ALTER TABLE fk_partitioned_fk_5 ATTACH PARTITION fk_partitioned_fk_5_1 FOR VALUE
 Partition of: fk_partitioned_fk_5 FOR VALUES FROM (0) TO (10)
 Foreign-key constraints:
     "fk_partitioned_fk_5_1_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b)
-    "fk_partitioned_fk_5_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE
-    "fk_partitioned_fk_5_a_fkey1" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
-    "fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
+    TABLE "fk_partitioned_fk_5" CONSTRAINT "fk_partitioned_fk_5_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE
+    TABLE "fk_partitioned_fk_5" CONSTRAINT "fk_partitioned_fk_5_a_fkey1" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
+    TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
 
 -- verify that attaching a table checks that the existing data satisfies the
 -- constraint
-- 
2.11.0

Reply via email to