From 984a6ced3ece409e97b74ce163297b8193eeeb6c Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 3 Mar 2006 03:30:54 +0000
Subject: [PATCH] Add CASCADE option to TRUNCATE.  Joachim Wieland

---
 doc/src/sgml/ref/truncate.sgml         | 42 ++++++++++--
 src/backend/catalog/heap.c             | 62 +++++++++++++++++-
 src/backend/commands/tablecmds.c       | 90 +++++++++++++++++++++-----
 src/backend/nodes/copyfuncs.c          |  3 +-
 src/backend/nodes/equalfuncs.c         |  3 +-
 src/backend/parser/gram.y              |  5 +-
 src/backend/tcop/utility.c             |  8 +--
 src/include/catalog/heap.h             |  4 +-
 src/include/commands/tablecmds.h       |  4 +-
 src/include/nodes/parsenodes.h         |  3 +-
 src/test/regress/expected/truncate.out | 54 +++++++++++++---
 src/test/regress/sql/truncate.sql      | 21 ++++++
 12 files changed, 252 insertions(+), 47 deletions(-)

diff --git a/doc/src/sgml/ref/truncate.sgml b/doc/src/sgml/ref/truncate.sgml
index fe68c5b334a..67d1a9843ee 100644
--- a/doc/src/sgml/ref/truncate.sgml
+++ b/doc/src/sgml/ref/truncate.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/truncate.sgml,v 1.19 2005/02/22 19:06:18 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/truncate.sgml,v 1.20 2006/03/03 03:30:52 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -20,7 +20,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ...]
+TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -47,6 +47,27 @@ TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ...]
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically truncate all tables that have foreign-key references
+      to any of the named tables, or to any tables added to the group
+      due to <literal>CASCADE</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to truncate if any of the tables have foreign-key references
+      from tables that are not to be truncated.  This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -61,7 +82,10 @@ TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ...]
    <command>TRUNCATE</> cannot be used on a table that has foreign-key
    references from other tables, unless all such tables are also truncated
    in the same command.  Checking validity in such cases would require table
-   scans, and the whole point is not to do one.
+   scans, and the whole point is not to do one.  The <literal>CASCADE</>
+   option can be used to automatically include all dependent tables &mdash;
+   but be very careful when using this option, else you might lose data you
+   did not intend to!
   </para>
 
   <para>
@@ -78,10 +102,20 @@ TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ...]
 
 <programlisting>
 TRUNCATE TABLE bigtable, fattable;
+</programlisting>
+  </para>
+
+  <para>
+   Truncate the table <literal>othertable</literal>, and cascade to any tables
+   that are referencing <literal>othertable</literal> via foreign-key
+   constraints:
+
+<programlisting>
+TRUNCATE othertable CASCADE;
 </programlisting>
   </para>
  </refsect1>
- 
+
  <refsect1>
   <title>Compatibility</title>
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b79b9f07086..ae86c44c38b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.293 2005/11/22 18:17:08 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.294 2006/03/03 03:30:52 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -2043,7 +2043,7 @@ heap_truncate_check_FKs(List *relations, bool tempTables)
 		if (con->contype != CONSTRAINT_FOREIGN)
 			continue;
 
-		/* Not for one of our list of tables */
+		/* Not referencing one of our list of tables */
 		if (!list_member_oid(oids, con->confrelid))
 			continue;
 
@@ -2066,7 +2066,8 @@ heap_truncate_check_FKs(List *relations, bool tempTables)
 								   get_rel_name(con->conrelid),
 								   get_rel_name(con->confrelid),
 								   NameStr(con->conname)),
-						 errhint("Truncate table \"%s\" at the same time.",
+						 errhint("Truncate table \"%s\" at the same time, "
+								 "or use TRUNCATE ... CASCADE.",
 								 get_rel_name(con->conrelid))));
 		}
 	}
@@ -2074,3 +2075,58 @@ heap_truncate_check_FKs(List *relations, bool tempTables)
 	systable_endscan(fkeyScan);
 	heap_close(fkeyRel, AccessShareLock);
 }
+
+/*
+ * heap_truncate_find_FKs
+ *		Find relations having foreign keys referencing any relations that
+ *		are to be truncated
+ *
+ * This is almost the same code as heap_truncate_check_FKs, but we don't
+ * raise an error if we find such relations; instead we return a list of
+ * their OIDs.  Also note that the input is a list of OIDs not a list
+ * of Relations.  The result list does *not* include any rels that are
+ * already in the input list.
+ *
+ * Note: caller should already have exclusive lock on all rels mentioned
+ * in relationIds.  Since adding or dropping an FK requires exclusive lock
+ * on both rels, this ensures that the answer will be stable.
+ */
+List *
+heap_truncate_find_FKs(List *relationIds)
+{
+	List	   *result = NIL;
+	Relation	fkeyRel;
+	SysScanDesc fkeyScan;
+	HeapTuple	tuple;
+
+	/*
+	 * Must scan pg_constraint.  Right now, it is a seqscan because
+	 * there is no available index on confrelid.
+	 */
+	fkeyRel = heap_open(ConstraintRelationId, AccessShareLock);
+
+	fkeyScan = systable_beginscan(fkeyRel, InvalidOid, false,
+								  SnapshotNow, 0, NULL);
+
+	while (HeapTupleIsValid(tuple = systable_getnext(fkeyScan)))
+	{
+		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+
+		/* Not a foreign key */
+		if (con->contype != CONSTRAINT_FOREIGN)
+			continue;
+
+		/* Not referencing one of our list of tables */
+		if (!list_member_oid(relationIds, con->confrelid))
+			continue;
+
+		/* Add referencer unless already in input or result list */
+		if (!list_member_oid(relationIds, con->conrelid))
+			result = list_append_unique_oid(result, con->conrelid);
+	}
+
+	systable_endscan(fkeyScan);
+	heap_close(fkeyRel, AccessShareLock);
+
+	return result;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 48ebd597c99..4a3c146faa0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.177 2006/01/30 16:18:58 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.178 2006/03/03 03:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -527,26 +527,79 @@ RemoveRelation(const RangeVar *relation, DropBehavior behavior)
  * ExecuteTruncate
  *		Executes a TRUNCATE command.
  *
- * This is a multi-relation truncate.  It first opens and grabs exclusive
- * locks on all relations involved, checking permissions and otherwise
- * verifying that the relation is OK for truncation.  When they are all
- * open, it checks foreign key references on them, namely that FK references
- * are all internal to the group that's being truncated.  Finally all
- * relations are truncated and reindexed.
+ * This is a multi-relation truncate.  We first open and grab exclusive
+ * lock on all relations involved, checking permissions and otherwise
+ * verifying that the relation is OK for truncation.  In CASCADE mode,
+ * relations having FK references to the targeted relations are automatically
+ * added to the group; in RESTRICT mode, we check that all FK references are
+ * internal to the group that's being truncated.  Finally all the relations
+ * are truncated and reindexed.
  */
 void
-ExecuteTruncate(List *relations)
+ExecuteTruncate(TruncateStmt *stmt)
 {
 	List	   *rels = NIL;
+	List	   *directRelids = NIL;
 	ListCell   *cell;
+	Oid			relid;
+	Relation	rel;
 
-	foreach(cell, relations)
+	/*
+	 * Open and exclusive-lock all the explicitly-specified relations
+	 */
+	foreach(cell, stmt->relations)
 	{
 		RangeVar   *rv = lfirst(cell);
-		Relation	rel;
 
-		/* Grab exclusive lock in preparation for truncate */
 		rel = heap_openrv(rv, AccessExclusiveLock);
+		rels = lappend(rels, rel);
+		directRelids = lappend_oid(directRelids, RelationGetRelid(rel));
+	}
+
+	/*
+	 * In CASCADE mode, suck in all referencing relations as well.  This
+	 * requires multiple iterations to find indirectly-dependent relations.
+	 * At each phase, we need to exclusive-lock new rels before looking
+	 * for their dependencies, else we might miss something.
+	 */
+	if (stmt->behavior == DROP_CASCADE)
+	{
+		List   *relids = list_copy(directRelids);
+
+		for (;;)
+		{
+			List   *newrelids;
+
+			newrelids = heap_truncate_find_FKs(relids);
+			if (newrelids == NIL)
+				break;			/* nothing else to add */
+
+			foreach(cell, newrelids)
+			{
+				relid = lfirst_oid(cell);
+				rel = heap_open(relid, AccessExclusiveLock);
+				rels = lappend(rels, rel);
+				relids = lappend_oid(relids, relid);
+			}
+		}
+	}
+
+	/* now check all involved relations */
+	foreach(cell, rels)
+	{
+		rel = (Relation) lfirst(cell);
+		relid = RelationGetRelid(rel);
+
+		/*
+		 * If this table was added to the command by CASCADE, report it.
+		 * We don't do this earlier because if we error out on one of the
+		 * tables, it'd be confusing to list subsequently-added tables.
+		 */
+		if (stmt->behavior == DROP_CASCADE &&
+			!list_member_oid(directRelids, relid))
+			ereport(NOTICE,
+					(errmsg("truncate cascades to table \"%s\"",
+							RelationGetRelationName(rel))));
 
 		/* Only allow truncate on regular tables */
 		if (rel->rd_rel->relkind != RELKIND_RELATION)
@@ -585,25 +638,30 @@ ExecuteTruncate(List *relations)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			  errmsg("cannot truncate temporary tables of other sessions")));
-
-		/* Save it into the list of rels to truncate */
-		rels = lappend(rels, rel);
 	}
 
 	/*
-	 * Check foreign key references.
+	 * Check foreign key references.  In CASCADE mode, this should be
+	 * unnecessary since we just pulled in all the references; but as
+	 * a cross-check, do it anyway if in an Assert-enabled build.
 	 */
+#ifdef USE_ASSERT_CHECKING
 	heap_truncate_check_FKs(rels, false);
+#else
+	if (stmt->behavior == DROP_RESTRICT)
+		heap_truncate_check_FKs(rels, false);
+#endif
 
 	/*
 	 * OK, truncate each table.
 	 */
 	foreach(cell, rels)
 	{
-		Relation	rel = lfirst(cell);
 		Oid			heap_relid;
 		Oid			toast_relid;
 
+		rel = (Relation) lfirst(cell);
+
 		/*
 		 * Create a new empty storage file for the relation, and assign it as
 		 * the relfilenode value.	The old storage file is scheduled for
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 91f06df039a..1e79857d807 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.327 2006/02/19 00:04:26 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.328 2006/03/03 03:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1961,6 +1961,7 @@ _copyTruncateStmt(TruncateStmt *from)
 	TruncateStmt *newnode = makeNode(TruncateStmt);
 
 	COPY_NODE_FIELD(relations);
+	COPY_SCALAR_FIELD(behavior);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9a2a5fd0d59..6c255548042 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.263 2006/02/19 00:04:26 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.264 2006/03/03 03:30:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -930,6 +930,7 @@ static bool
 _equalTruncateStmt(TruncateStmt *a, TruncateStmt *b)
 {
 	COMPARE_NODE_FIELD(relations);
+	COMPARE_SCALAR_FIELD(behavior);
 
 	return true;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a60d4157b2e..92f0ba67d73 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.531 2006/02/28 22:37:26 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.532 2006/03/03 03:30:53 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -2938,10 +2938,11 @@ attrs:		'.' attr_name
  *****************************************************************************/
 
 TruncateStmt:
-			TRUNCATE opt_table qualified_name_list
+			TRUNCATE opt_table qualified_name_list opt_drop_behavior
 				{
 					TruncateStmt *n = makeNode(TruncateStmt);
 					n->relations = $3;
+					n->behavior = $4;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b55b1578b38..9a9d138032b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.252 2006/02/12 19:11:01 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.253 2006/03/03 03:30:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -628,11 +628,7 @@ ProcessUtility(Node *parsetree,
 			break;
 
 		case T_TruncateStmt:
-			{
-				TruncateStmt *stmt = (TruncateStmt *) parsetree;
-
-				ExecuteTruncate(stmt->relations);
-			}
+			ExecuteTruncate((TruncateStmt *) parsetree);
 			break;
 
 		case T_CommentStmt:
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 9cb4c5351a8..09516ed0215 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/heap.h,v 1.76 2005/10/15 02:49:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/heap.h,v 1.77 2006/03/03 03:30:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -62,6 +62,8 @@ extern void heap_truncate(List *relids);
 
 extern void heap_truncate_check_FKs(List *relations, bool tempTables);
 
+extern List *heap_truncate_find_FKs(List *relationIds);
+
 extern List *AddRelationRawConstraints(Relation rel,
 						  List *rawColDefaults,
 						  List *rawConstraints);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index b6e30f6f699..1e367210d70 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.25 2005/11/21 12:49:32 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.26 2006/03/03 03:30:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -36,7 +36,7 @@ extern void AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
 							   Oid oldNspOid, Oid newNspOid,
 							   bool hasDependEntry);
 
-extern void ExecuteTruncate(List *relations);
+extern void ExecuteTruncate(TruncateStmt *stmt);
 
 extern void renameatt(Oid myrelid,
 		  const char *oldattname,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a251e0759d3..f1f7d4456dd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.301 2006/02/19 00:04:27 neilc Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.302 2006/03/03 03:30:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1314,6 +1314,7 @@ typedef struct TruncateStmt
 {
 	NodeTag		type;
 	List	   *relations;		/* relations (RangeVars) to be truncated */
+	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } TruncateStmt;
 
 /* ----------------------
diff --git a/src/test/regress/expected/truncate.out b/src/test/regress/expected/truncate.out
index c03deef1e8e..263c5c8e083 100644
--- a/src/test/regress/expected/truncate.out
+++ b/src/test/regress/expected/truncate.out
@@ -40,30 +40,37 @@ CREATE TABLE trunc_e (a int REFERENCES truncate_a, b int REFERENCES trunc_c);
 TRUNCATE TABLE truncate_a;		-- fail
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "trunc_b" references "truncate_a" via foreign key constraint "trunc_b_a_fkey".
-HINT:  Truncate table "trunc_b" at the same time.
+HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE truncate_a,trunc_b;		-- fail
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "trunc_e" references "truncate_a" via foreign key constraint "trunc_e_a_fkey".
-HINT:  Truncate table "trunc_e" at the same time.
+HINT:  Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE truncate_a,trunc_b,trunc_e;	-- ok
 TRUNCATE TABLE truncate_a,trunc_e;		-- fail
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "trunc_b" references "truncate_a" via foreign key constraint "trunc_b_a_fkey".
-HINT:  Truncate table "trunc_b" at the same time.
+HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE trunc_c;		-- fail
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "trunc_d" references "trunc_c" via foreign key constraint "trunc_d_a_fkey".
-HINT:  Truncate table "trunc_d" at the same time.
+HINT:  Truncate table "trunc_d" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE trunc_c,trunc_d;		-- fail
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "trunc_e" references "trunc_c" via foreign key constraint "trunc_e_b_fkey".
-HINT:  Truncate table "trunc_e" at the same time.
+HINT:  Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE trunc_c,trunc_d,trunc_e;	-- ok
 TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a;	-- fail
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "trunc_b" references "truncate_a" via foreign key constraint "trunc_b_a_fkey".
-HINT:  Truncate table "trunc_b" at the same time.
+HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a,trunc_b;	-- ok
+TRUNCATE TABLE truncate_a RESTRICT; -- fail
+ERROR:  cannot truncate a table referenced in a foreign key constraint
+DETAIL:  Table "trunc_b" references "truncate_a" via foreign key constraint "trunc_b_a_fkey".
+HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
+TRUNCATE TABLE truncate_a CASCADE;  -- ok
+NOTICE:  truncate cascades to table "trunc_b"
+NOTICE:  truncate cascades to table "trunc_e"
 -- circular references
 ALTER TABLE truncate_a ADD FOREIGN KEY (col1) REFERENCES trunc_c;
 -- Add some data to verify that truncating actually works ...
@@ -75,19 +82,19 @@ INSERT INTO trunc_e VALUES (1,1);
 TRUNCATE TABLE trunc_c;
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "trunc_d" references "trunc_c" via foreign key constraint "trunc_d_a_fkey".
-HINT:  Truncate table "trunc_d" at the same time.
+HINT:  Truncate table "trunc_d" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE trunc_c,trunc_d;
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "trunc_e" references "trunc_c" via foreign key constraint "trunc_e_b_fkey".
-HINT:  Truncate table "trunc_e" at the same time.
+HINT:  Truncate table "trunc_e" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE trunc_c,trunc_d,trunc_e;
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "truncate_a" references "trunc_c" via foreign key constraint "truncate_a_col1_fkey".
-HINT:  Truncate table "truncate_a" at the same time.
+HINT:  Truncate table "truncate_a" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a;
 ERROR:  cannot truncate a table referenced in a foreign key constraint
 DETAIL:  Table "trunc_b" references "truncate_a" via foreign key constraint "trunc_b_a_fkey".
-HINT:  Truncate table "trunc_b" at the same time.
+HINT:  Truncate table "trunc_b" at the same time, or use TRUNCATE ... CASCADE.
 TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a,trunc_b;
 -- Verify that truncating did actually work
 SELECT * FROM truncate_a
@@ -106,6 +113,33 @@ SELECT * FROM trunc_e;
 ---+---
 (0 rows)
 
+-- Add data again to test TRUNCATE ... CASCADE
+INSERT INTO trunc_c VALUES (1);
+INSERT INTO truncate_a VALUES (1);
+INSERT INTO trunc_b VALUES (1);
+INSERT INTO trunc_d VALUES (1);
+INSERT INTO trunc_e VALUES (1,1);
+TRUNCATE TABLE trunc_c CASCADE;  -- ok
+NOTICE:  truncate cascades to table "trunc_d"
+NOTICE:  truncate cascades to table "trunc_e"
+NOTICE:  truncate cascades to table "truncate_a"
+NOTICE:  truncate cascades to table "trunc_b"
+SELECT * FROM truncate_a
+   UNION ALL
+ SELECT * FROM trunc_c
+   UNION ALL
+ SELECT * FROM trunc_b
+   UNION ALL
+ SELECT * FROM trunc_d;
+ col1 
+------
+(0 rows)
+
+SELECT * FROM trunc_e;
+ a | b 
+---+---
+(0 rows)
+
 DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
 NOTICE:  drop cascades to constraint trunc_e_a_fkey on table trunc_e
 NOTICE:  drop cascades to constraint trunc_b_a_fkey on table trunc_b
diff --git a/src/test/regress/sql/truncate.sql b/src/test/regress/sql/truncate.sql
index 09a08e5652a..a4d27d01e1f 100644
--- a/src/test/regress/sql/truncate.sql
+++ b/src/test/regress/sql/truncate.sql
@@ -30,6 +30,9 @@ TRUNCATE TABLE trunc_c,trunc_d,trunc_e;	-- ok
 TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a;	-- fail
 TRUNCATE TABLE trunc_c,trunc_d,trunc_e,truncate_a,trunc_b;	-- ok
 
+TRUNCATE TABLE truncate_a RESTRICT; -- fail
+TRUNCATE TABLE truncate_a CASCADE;  -- ok
+
 -- circular references
 ALTER TABLE truncate_a ADD FOREIGN KEY (col1) REFERENCES trunc_c;
 
@@ -55,4 +58,22 @@ SELECT * FROM truncate_a
  SELECT * FROM trunc_d;
 SELECT * FROM trunc_e;
 
+-- Add data again to test TRUNCATE ... CASCADE
+INSERT INTO trunc_c VALUES (1);
+INSERT INTO truncate_a VALUES (1);
+INSERT INTO trunc_b VALUES (1);
+INSERT INTO trunc_d VALUES (1);
+INSERT INTO trunc_e VALUES (1,1);
+
+TRUNCATE TABLE trunc_c CASCADE;  -- ok
+
+SELECT * FROM truncate_a
+   UNION ALL
+ SELECT * FROM trunc_c
+   UNION ALL
+ SELECT * FROM trunc_b
+   UNION ALL
+ SELECT * FROM trunc_d;
+SELECT * FROM trunc_e;
+
 DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
-- 
GitLab