From c4f2a0458dc029d1214f013f1434f70f5194e56d Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 11 Jun 2008 21:53:49 +0000
Subject: [PATCH] Improve reporting of dependencies in DROP to work like the
 scheme that we devised for pg_shdepend, namely the individual dependencies
 are reported as DETAIL lines rather than coming out as separate NOTICEs.  The
 client-side report is capped at 100 lines, but the server log always gets a
 full report.

---
 src/backend/catalog/dependency.c          | 119 ++++++++++++++++++++--
 src/test/regress/expected/alter_table.out |  16 +--
 src/test/regress/expected/create_view.out |  78 +++++++-------
 src/test/regress/expected/domain.out      |  12 ++-
 src/test/regress/expected/foreign_key.out |  21 ++--
 src/test/regress/expected/inherit.out     |  12 ++-
 src/test/regress/expected/namespace.out   |   5 +-
 src/test/regress/expected/sequence.out    |   4 +-
 src/test/regress/expected/truncate.out    |  10 +-
 src/test/regress/output/tablespace.source |   9 +-
 10 files changed, 199 insertions(+), 87 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 12b654504d7..8ca95a3fd65 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.74 2008/06/08 22:41:04 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.75 2008/06/11 21:53:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -62,6 +62,7 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
+#include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
@@ -752,7 +753,7 @@ findDependentObjects(const ObjectAddress *object,
  *
  *	targetObjects: list of objects that are scheduled to be deleted
  *	behavior: RESTRICT or CASCADE
- *	msglevel: elog level for non-debug notice messages
+ *	msglevel: elog level for non-error report messages
  *	origObject: base object of deletion, or NULL if not available
  *		(the latter case occurs in DROP OWNED)
  */
@@ -763,8 +764,36 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 					   const ObjectAddress *origObject)
 {
 	bool		ok = true;
+	StringInfoData clientdetail;
+	StringInfoData logdetail;
+	int			numReportedClient = 0;
+	int			numNotReportedClient = 0;
 	int			i;
 
+	/*
+	 * If no error is to be thrown, and the msglevel is too low to be shown
+	 * to either client or server log, there's no need to do any of the work.
+	 *
+	 * Note: this code doesn't know all there is to be known about elog
+	 * levels, but it works for NOTICE and DEBUG2, which are the only values
+	 * msglevel can currently have.  We also assume we are running in a normal
+	 * operating environment.
+	 */
+	if (behavior == DROP_CASCADE &&
+		msglevel < client_min_messages &&
+		(msglevel < log_min_messages || log_min_messages == LOG))
+		return;
+
+	/*
+	 * We limit the number of dependencies reported to the client to
+	 * MAX_REPORTED_DEPS, since client software may not deal well with
+	 * enormous error strings.	The server log always gets a full report.
+	 */
+#define MAX_REPORTED_DEPS 100
+
+	initStringInfo(&clientdetail);
+	initStringInfo(&logdetail);
+
 	/*
 	 * We process the list back to front (ie, in dependency order not deletion
 	 * order), since this makes for a more understandable display.
@@ -773,34 +802,82 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 	{
 		const ObjectAddress *obj = &targetObjects->refs[i];
 		const ObjectAddressExtra *extra = &targetObjects->extras[i];
+		char	   *objDesc;
 
 		/* Ignore the original deletion target(s) */
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
+		objDesc = getObjectDescription(obj);
+
 		/*
 		 * If, at any stage of the recursive search, we reached the object
 		 * via an AUTO or INTERNAL dependency, then it's okay to delete it
 		 * even in RESTRICT mode.
 		 */
 		if (extra->flags & (DEPFLAG_AUTO | DEPFLAG_INTERNAL))
+		{
+			/*
+			 * auto-cascades are reported at DEBUG2, not msglevel.  We
+			 * don't try to combine them with the regular message because
+			 * the results are too confusing when client_min_messages and
+			 * log_min_messages are different.
+			 */
 			ereport(DEBUG2,
 					(errmsg("drop auto-cascades to %s",
-							getObjectDescription(obj))));
+							objDesc)));
+		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			ereport(msglevel,
-					(errmsg("%s depends on %s",
-							getObjectDescription(obj),
-							getObjectDescription(&extra->dependee))));
+			char   *otherDesc = getObjectDescription(&extra->dependee);
+
+			if (numReportedClient < MAX_REPORTED_DEPS)
+			{
+				/* separate entries with a newline */
+				if (clientdetail.len != 0)
+					appendStringInfoChar(&clientdetail, '\n');
+				appendStringInfo(&clientdetail, _("%s depends on %s"),
+								 objDesc, otherDesc);
+				numReportedClient++;
+			}
+			else
+				numNotReportedClient++;
+			/* separate entries with a newline */
+			if (logdetail.len != 0)
+				appendStringInfoChar(&logdetail, '\n');
+			appendStringInfo(&logdetail, _("%s depends on %s"),
+							 objDesc, otherDesc);
+			pfree(otherDesc);
 			ok = false;
 		}
 		else
-			ereport(msglevel,
-					(errmsg("drop cascades to %s",
-							getObjectDescription(obj))));
+		{
+			if (numReportedClient < MAX_REPORTED_DEPS)
+			{
+				/* separate entries with a newline */
+				if (clientdetail.len != 0)
+					appendStringInfoChar(&clientdetail, '\n');
+				appendStringInfo(&clientdetail, _("drop cascades to %s"),
+								 objDesc);
+				numReportedClient++;
+			}
+			else
+				numNotReportedClient++;
+			/* separate entries with a newline */
+			if (logdetail.len != 0)
+				appendStringInfoChar(&logdetail, '\n');
+			appendStringInfo(&logdetail, _("drop cascades to %s"),
+							 objDesc);
+		}
+
+		pfree(objDesc);
 	}
 
+	if (numNotReportedClient > 0)
+		appendStringInfo(&clientdetail, _("\nand %d other objects "
+										  "(see server log for list)"),
+						 numNotReportedClient);
+
 	if (!ok)
 	{
 		if (origObject)
@@ -808,13 +885,35 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
 							getObjectDescription(origObject)),
+					 errdetail("%s", clientdetail.data),
+					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop desired object(s) because other objects depend on them"),
+					 errdetail("%s", clientdetail.data),
+					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
 	}
+	else if (numReportedClient > 1)
+	{
+		ereport(msglevel,
+				/* translator: %d always has a value larger than 1 */
+				(errmsg("drop cascades to %d other objects",
+						numReportedClient + numNotReportedClient),
+				 errdetail("%s", clientdetail.data),
+				 errdetail_log("%s", logdetail.data)));
+	}
+	else if (numReportedClient == 1)
+	{
+		/* we just use the single item as-is */
+		ereport(msglevel,
+				(errmsg_internal("%s", clientdetail.data)));
+	}
+
+	pfree(clientdetail.data);
+	pfree(logdetail.data);
 }
 
 /*
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index e7aef8bf59d..daf9482c4ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1161,8 +1161,9 @@ order by relname, attnum;
 (8 rows)
 
 drop table p1, p2 cascade;
-NOTICE:  drop cascades to table c1
-NOTICE:  drop cascades to table gc1
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table c1
+drop cascades to table gc1
 --
 -- Test the ALTER TABLE WITHOUT OIDS command
 --
@@ -1469,8 +1470,9 @@ select alter2.plus1(41);
 
 -- clean up
 drop schema alter2 cascade;
-NOTICE:  drop cascades to table alter2.t1
-NOTICE:  drop cascades to view alter2.v1
-NOTICE:  drop cascades to function alter2.plus1(integer)
-NOTICE:  drop cascades to type alter2.posint
-NOTICE:  drop cascades to type alter2.ctype
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table alter2.t1
+drop cascades to view alter2.v1
+drop cascades to function alter2.plus1(integer)
+drop cascades to type alter2.posint
+drop cascades to type alter2.ctype
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index 3eae7e90ccd..cbee9dceed8 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -237,43 +237,45 @@ And relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname LIKE 'pg_temp%')
 (1 row)
 
 DROP SCHEMA temp_view_test CASCADE;
-NOTICE:  drop cascades to table temp_view_test.base_table
-NOTICE:  drop cascades to view v7_temp
-NOTICE:  drop cascades to view v10_temp
-NOTICE:  drop cascades to view v11_temp
-NOTICE:  drop cascades to view v12_temp
-NOTICE:  drop cascades to view v2_temp
-NOTICE:  drop cascades to view v4_temp
-NOTICE:  drop cascades to view v6_temp
-NOTICE:  drop cascades to view v8_temp
-NOTICE:  drop cascades to view v9_temp
-NOTICE:  drop cascades to table temp_view_test.base_table2
-NOTICE:  drop cascades to view v5_temp
-NOTICE:  drop cascades to view temp_view_test.v1
-NOTICE:  drop cascades to view temp_view_test.v2
-NOTICE:  drop cascades to view temp_view_test.v3
-NOTICE:  drop cascades to view temp_view_test.v4
-NOTICE:  drop cascades to view temp_view_test.v5
-NOTICE:  drop cascades to view temp_view_test.v6
-NOTICE:  drop cascades to view temp_view_test.v7
-NOTICE:  drop cascades to view temp_view_test.v8
-NOTICE:  drop cascades to sequence temp_view_test.seq1
-NOTICE:  drop cascades to view temp_view_test.v9
+NOTICE:  drop cascades to 22 other objects
+DETAIL:  drop cascades to table temp_view_test.base_table
+drop cascades to view v7_temp
+drop cascades to view v10_temp
+drop cascades to view v11_temp
+drop cascades to view v12_temp
+drop cascades to view v2_temp
+drop cascades to view v4_temp
+drop cascades to view v6_temp
+drop cascades to view v8_temp
+drop cascades to view v9_temp
+drop cascades to table temp_view_test.base_table2
+drop cascades to view v5_temp
+drop cascades to view temp_view_test.v1
+drop cascades to view temp_view_test.v2
+drop cascades to view temp_view_test.v3
+drop cascades to view temp_view_test.v4
+drop cascades to view temp_view_test.v5
+drop cascades to view temp_view_test.v6
+drop cascades to view temp_view_test.v7
+drop cascades to view temp_view_test.v8
+drop cascades to sequence temp_view_test.seq1
+drop cascades to view temp_view_test.v9
 DROP SCHEMA testviewschm2 CASCADE;
-NOTICE:  drop cascades to table t1
-NOTICE:  drop cascades to view temporal1
-NOTICE:  drop cascades to view temporal2
-NOTICE:  drop cascades to view temporal3
-NOTICE:  drop cascades to view temporal4
-NOTICE:  drop cascades to table t2
-NOTICE:  drop cascades to view nontemp1
-NOTICE:  drop cascades to view nontemp2
-NOTICE:  drop cascades to view nontemp3
-NOTICE:  drop cascades to view nontemp4
-NOTICE:  drop cascades to table tbl1
-NOTICE:  drop cascades to table tbl2
-NOTICE:  drop cascades to table tbl3
-NOTICE:  drop cascades to table tbl4
-NOTICE:  drop cascades to view mytempview
-NOTICE:  drop cascades to view pubview
+NOTICE:  drop cascades to 16 other objects
+DETAIL:  drop cascades to table t1
+drop cascades to view temporal1
+drop cascades to view temporal2
+drop cascades to view temporal3
+drop cascades to view temporal4
+drop cascades to table t2
+drop cascades to view nontemp1
+drop cascades to view nontemp2
+drop cascades to view nontemp3
+drop cascades to view nontemp4
+drop cascades to table tbl1
+drop cascades to table tbl2
+drop cascades to table tbl3
+drop cascades to table tbl4
+drop cascades to view mytempview
+drop cascades to view pubview
 SET search_path to public;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 179c8c63471..fd88b16ccee 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -7,8 +7,8 @@ comment on domain domaindroptest is 'About to drop this..';
 create domain dependenttypetest domaindroptest;
 -- fail because of dependent type
 drop domain domaindroptest;
-NOTICE:  type dependenttypetest depends on type domaindroptest
 ERROR:  cannot drop type domaindroptest because other objects depend on it
+DETAIL:  type dependenttypetest depends on type domaindroptest
 HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 drop domain domaindroptest cascade;
 NOTICE:  drop cascades to type dependenttypetest
@@ -266,8 +266,9 @@ ERROR:  domain dnotnulltest does not allow null values
 alter domain dnotnulltest drop not null;
 update domnotnull set col1 = null;
 drop domain dnotnulltest cascade;
-NOTICE:  drop cascades to table domnotnull column col1
-NOTICE:  drop cascades to table domnotnull column col2
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table domnotnull column col1
+drop cascades to table domnotnull column col2
 -- Test ALTER DOMAIN .. DEFAULT ..
 create table domdeftest (col1 ddef1);
 insert into domdeftest default values;
@@ -395,8 +396,9 @@ insert into dtest values('xz23'); -- fail
 ERROR:  value for domain dtop violates check constraint "dtop_check"
 drop table dtest;
 drop domain vchar4 cascade;
-NOTICE:  drop cascades to type dinter
-NOTICE:  drop cascades to type dtop
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to type dinter
+drop cascades to type dtop
 -- Make sure that constraints of newly-added domain columns are
 -- enforced correctly, even if there's no default value for the new
 -- column. Per bug #1433
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index 87e6591ea35..e3086d2e085 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -257,8 +257,8 @@ SELECT * FROM FKTABLE;
 
 -- this should fail for lack of CASCADE
 DROP TABLE PKTABLE;
-NOTICE:  constraint constrname2 on table fktable depends on table pktable
 ERROR:  cannot drop table pktable because other objects depend on it
+DETAIL:  constraint constrname2 on table fktable depends on table pktable
 HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 DROP TABLE PKTABLE CASCADE;
 NOTICE:  drop cascades to constraint constrname2 on table fktable
@@ -1157,15 +1157,16 @@ FOREIGN KEY (x2,x4,x1) REFERENCES pktable(id1,id3,id2);
 ERROR:  foreign key constraint "fk_241_132" cannot be implemented
 DETAIL:  Key columns "x2" and "id1" are of incompatible types: character varying and integer.
 DROP TABLE pktable, fktable CASCADE;
-NOTICE:  drop cascades to constraint fktable_x3_fkey on table fktable
-NOTICE:  drop cascades to constraint fk_1_3 on table fktable
-NOTICE:  drop cascades to constraint fktable_x2_fkey on table fktable
-NOTICE:  drop cascades to constraint fk_4_2 on table fktable
-NOTICE:  drop cascades to constraint fktable_x1_fkey on table fktable
-NOTICE:  drop cascades to constraint fk_5_1 on table fktable
-NOTICE:  drop cascades to constraint fk_123_123 on table fktable
-NOTICE:  drop cascades to constraint fk_213_213 on table fktable
-NOTICE:  drop cascades to constraint fk_253_213 on table fktable
+NOTICE:  drop cascades to 9 other objects
+DETAIL:  drop cascades to constraint fktable_x3_fkey on table fktable
+drop cascades to constraint fk_1_3 on table fktable
+drop cascades to constraint fktable_x2_fkey on table fktable
+drop cascades to constraint fk_4_2 on table fktable
+drop cascades to constraint fktable_x1_fkey on table fktable
+drop cascades to constraint fk_5_1 on table fktable
+drop cascades to constraint fk_123_123 on table fktable
+drop cascades to constraint fk_213_213 on table fktable
+drop cascades to constraint fk_253_213 on table fktable
 -- test a tricky case: we can elide firing the FK check trigger during
 -- an UPDATE if the UPDATE did not change the foreign key
 -- field. However, we can't do this if our transaction was the one that
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 7c7ac0ef338..100edb35396 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -844,9 +844,10 @@ Inherits: c1,
           c2
 
 drop table p1 cascade;
-NOTICE:  drop cascades to table c1
-NOTICE:  drop cascades to table c2
-NOTICE:  drop cascades to table c3
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table c1
+drop cascades to table c2
+drop cascades to table c3
 drop table p2 cascade;
 create table pp1 (f1 int);
 create table cc1 (f2 text, f3 int) inherits (pp1);
@@ -900,5 +901,6 @@ Inherits: pp1,
           cc1
 
 drop table pp1 cascade;
-NOTICE:  drop cascades to table cc1
-NOTICE:  drop cascades to table cc2
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table cc1
+drop cascades to table cc2
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index 94b3e5e99b0..58a0c4dc81d 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -39,8 +39,9 @@ SELECT * FROM test_schema_1.abc_view;
 (3 rows)
 
 DROP SCHEMA test_schema_1 CASCADE;
-NOTICE:  drop cascades to table test_schema_1.abc
-NOTICE:  drop cascades to view test_schema_1.abc_view
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table test_schema_1.abc
+drop cascades to view test_schema_1.abc_view
 -- verify that the objects were dropped
 SELECT COUNT(*) FROM pg_class WHERE relnamespace =
     (SELECT oid FROM pg_namespace WHERE nspname = 'test_schema_1');
diff --git a/src/test/regress/expected/sequence.out b/src/test/regress/expected/sequence.out
index 84ece98357b..823039ae955 100644
--- a/src/test/regress/expected/sequence.out
+++ b/src/test/regress/expected/sequence.out
@@ -148,12 +148,12 @@ CREATE TEMP TABLE t1 (
 NOTICE:  CREATE TABLE will create implicit sequence "t1_f1_seq" for serial column "t1.f1"
 -- Both drops should fail, but with different error messages:
 DROP SEQUENCE t1_f1_seq;
-NOTICE:  default for table t1 column f1 depends on sequence t1_f1_seq
 ERROR:  cannot drop sequence t1_f1_seq because other objects depend on it
+DETAIL:  default for table t1 column f1 depends on sequence t1_f1_seq
 HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 DROP SEQUENCE myseq2;
-NOTICE:  default for table t1 column f2 depends on sequence myseq2
 ERROR:  cannot drop sequence myseq2 because other objects depend on it
+DETAIL:  default for table t1 column f2 depends on sequence myseq2
 HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 -- This however will work:
 DROP SEQUENCE myseq3;
diff --git a/src/test/regress/expected/truncate.out b/src/test/regress/expected/truncate.out
index d0a99a554a6..520db9d9b17 100644
--- a/src/test/regress/expected/truncate.out
+++ b/src/test/regress/expected/truncate.out
@@ -141,10 +141,12 @@ SELECT * FROM trunc_e;
 (0 rows)
 
 DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
-NOTICE:  drop cascades to constraint trunc_b_a_fkey on table trunc_b
-NOTICE:  drop cascades to constraint trunc_e_a_fkey on table trunc_e
-NOTICE:  drop cascades to constraint trunc_d_a_fkey on table trunc_d
-NOTICE:  drop cascades to constraint trunc_e_b_fkey on table trunc_e
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to constraint trunc_b_a_fkey on table trunc_b
+drop cascades to constraint trunc_e_a_fkey on table trunc_e
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to constraint trunc_d_a_fkey on table trunc_d
+drop cascades to constraint trunc_e_b_fkey on table trunc_e
 -- Test ON TRUNCATE triggers
 CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);
 CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text,
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 6337798c625..8065505be95 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -65,9 +65,10 @@ ERROR:  tablespace "nosuchspace" does not exist
 DROP TABLESPACE testspace;
 ERROR:  tablespace "testspace" is not empty
 DROP SCHEMA testschema CASCADE;
-NOTICE:  drop cascades to table testschema.foo
-NOTICE:  drop cascades to table testschema.asselect
-NOTICE:  drop cascades to table testschema.asexecute
-NOTICE:  drop cascades to table testschema.atable
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table testschema.foo
+drop cascades to table testschema.asselect
+drop cascades to table testschema.asexecute
+drop cascades to table testschema.atable
 -- Should succeed
 DROP TABLESPACE testspace;
-- 
GitLab