From c851e3d68993feac2f35c101e8ed244ffd6b0935 Mon Sep 17 00:00:00 2001
From: Wu Haotian <whtsky@gmail.com>
Date: Thu, 1 Apr 2021 17:14:29 +0800
Subject: [PATCH] pg_dump/restore: add --drop-cascade option

This option makes pg_dump, pg_dumpall and pg_store inject a CASCADE
clause to each DROP command.

This allows for restoring an old backup after adding new constraints,
at the risk of losing new data.
---
 doc/src/sgml/ref/pg_dump.sgml        |  10 ++
 doc/src/sgml/ref/pg_dumpall.sgml     |  10 ++
 doc/src/sgml/ref/pg_restore.sgml     |  10 ++
 src/bin/pg_dump/pg_backup.h          |   2 +
 src/bin/pg_dump/pg_backup_archiver.c | 152 ++++++++++++++-------------
 src/bin/pg_dump/pg_dump.c            |   6 ++
 src/bin/pg_dump/pg_dumpall.c         |  11 ++
 src/bin/pg_dump/pg_restore.c         |  10 ++
 8 files changed, 138 insertions(+), 73 deletions(-)

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 529b167c96..b486c26e39 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -732,6 +732,16 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--drop-cascade</option></term>
+      <listitem>
+       <para>
+        Use <literal>CASCADE</literal> to drop database objects.
+        This option is not valid unless <option>--clean</option> is also specified.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--enable-row-security</option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index 3c2f9c774f..fc5742b461 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -289,6 +289,16 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--drop-cascade</option></term>
+      <listitem>
+       <para>
+        Use <literal>CASCADE</literal> to drop database objects.
+        This option is not valid unless <option>--clean</option> is also specified.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--exclude-database=<replaceable class="parameter">pattern</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 93ea937ac8..d52067a149 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -553,6 +553,16 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--drop-cascade</option></term>
+      <listitem>
+       <para>
+        Use <literal>CASCADE</literal> to drop database objects.
+        This option is not valid unless <option>--clean</option> is also specified.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--enable-row-security</option></term>
       <listitem>
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 3bc86635f7..f06ea051a3 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -83,6 +83,7 @@ typedef struct _restoreOptions
 									 * instead of OWNER TO */
 	char	   *superuser;		/* Username to use as superuser */
 	char	   *use_role;		/* Issue SET ROLE to this */
+	int			drop_cascade;
 	int			dropSchema;
 	int			disable_dollar_quoting;
 	int			dump_inserts;	/* 0 = COPY, otherwise rows per INSERT */
@@ -152,6 +153,7 @@ typedef struct _dumpOptions
 
 	/* flags for various command-line long options */
 	int			disable_dollar_quoting;
+	int			drop_cascade;
 	int			column_inserts;
 	int			if_exists;
 	int			no_comments;
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 86de26a4bf..cb3e9b839c 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -187,6 +187,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt)
 	dopt->cparams.promptPassword = ropt->cparams.promptPassword;
 	dopt->outputClean = ropt->dropSchema;
 	dopt->dataOnly = ropt->dataOnly;
+	dopt->drop_cascade = ropt->drop_cascade;
 	dopt->schemaOnly = ropt->schemaOnly;
 	dopt->if_exists = ropt->if_exists;
 	dopt->column_inserts = ropt->column_inserts;
@@ -528,100 +529,105 @@ RestoreArchive(Archive *AHX)
 				 */
 				if (*te->dropStmt != '\0')
 				{
-					if (!ropt->if_exists)
+					if (!ropt->if_exists && !ropt->drop_cascade)
 					{
-						/* No --if-exists?	Then just use the original */
+						/* Just use the original */
 						ahprintf(AH, "%s", te->dropStmt);
 					}
+					/*
+					* Inject an appropriate spelling of "if exists" or "cascade".
+					* For large objects, we have a separate routine that
+					* knows how to do it, without depending on
+					* te->dropStmt; use that.  For other objects we need
+					* to parse the command.
+					*/
+					else if (strncmp(te->desc, "BLOB", 4) == 0)
+					{
+						if (ropt->if_exists)
+							DropBlobIfExists(AH, te->catalogId.oid);
+						else
+							ahprintf(AH, "%s", te->dropStmt);
+					}
 					else
 					{
+						char	   *dropStmt = pg_strdup(te->dropStmt);
+						char	   *dropStmtOrig = dropStmt;
+						PQExpBuffer ftStmt = createPQExpBuffer();
 						/*
-						 * Inject an appropriate spelling of "if exists".  For
-						 * large objects, we have a separate routine that
-						 * knows how to do it, without depending on
-						 * te->dropStmt; use that.  For other objects we need
-						 * to parse the command.
-						 */
-						if (strncmp(te->desc, "BLOB", 4) == 0)
+						* Need to inject IF EXISTS clause after ALTER
+						* TABLE part in ALTER TABLE .. DROP statement
+						*/
+						if (ropt->if_exists && strncmp(dropStmt, "ALTER TABLE", 11) == 0)
 						{
-							DropBlobIfExists(AH, te->catalogId.oid);
+							appendPQExpBufferStr(ftStmt,
+													"ALTER TABLE IF EXISTS");
+							dropStmt = dropStmt + 11;
 						}
+
+						/*
+						* ALTER TABLE..ALTER COLUMN..DROP DEFAULT does
+						* not support the IF EXISTS or CASCADE clause, and therefore
+						* we simply emit the original command for DEFAULT
+						* objects (modulo the adjustment made above).
+						*
+						* Likewise, don't mess with DATABASE PROPERTIES.
+						*
+						* If we used CREATE OR REPLACE VIEW as a means of
+						* quasi-dropping an ON SELECT rule, that should
+						* be emitted unchanged as well.
+						*
+						* For other object types, we need to extract the
+						* first part of the DROP which includes the
+						* object type.  Most of the time this matches
+						* te->desc, so search for that; however for the
+						* different kinds of CONSTRAINTs, we know to
+						* search for hardcoded "DROP CONSTRAINT" instead.
+						*/
+						if (strcmp(te->desc, "DEFAULT") == 0 ||
+							strcmp(te->desc, "DATABASE PROPERTIES") == 0 ||
+							strncmp(dropStmt, "CREATE OR REPLACE VIEW", 22) == 0)
+							appendPQExpBufferStr(ftStmt, dropStmt);
 						else
 						{
-							char	   *dropStmt = pg_strdup(te->dropStmt);
-							char	   *dropStmtOrig = dropStmt;
-							PQExpBuffer ftStmt = createPQExpBuffer();
+							char		buffer[40];
+							char	   *mark;
 
-							/*
-							 * Need to inject IF EXISTS clause after ALTER
-							 * TABLE part in ALTER TABLE .. DROP statement
-							 */
-							if (strncmp(dropStmt, "ALTER TABLE", 11) == 0)
+							if (strcmp(te->desc, "CONSTRAINT") == 0 ||
+								strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
+								strcmp(te->desc, "FK CONSTRAINT") == 0)
+								strcpy(buffer, "DROP CONSTRAINT");
+							else
+								snprintf(buffer, sizeof(buffer), "DROP %s",
+											te->desc);
+
+							mark = strstr(dropStmt, buffer);
+
+							if (mark)
 							{
-								appendPQExpBufferStr(ftStmt,
-													 "ALTER TABLE IF EXISTS");
-								dropStmt = dropStmt + 11;
+								char *stmtEnd;
+								*mark = '\0';
+								stmtEnd = strstr(mark + strlen(buffer), ";");
+								*stmtEnd = '\0';
+								appendPQExpBuffer(ftStmt, "%s%s %s%s%s%s",
+													dropStmt, buffer,
+													ropt->if_exists ? "IF EXISTS" : "",
+													mark + strlen(buffer),
+													ropt->drop_cascade ? " CASCADE;" : ";",
+													stmtEnd + 1);
 							}
-
-							/*
-							 * ALTER TABLE..ALTER COLUMN..DROP DEFAULT does
-							 * not support the IF EXISTS clause, and therefore
-							 * we simply emit the original command for DEFAULT
-							 * objects (modulo the adjustment made above).
-							 *
-							 * Likewise, don't mess with DATABASE PROPERTIES.
-							 *
-							 * If we used CREATE OR REPLACE VIEW as a means of
-							 * quasi-dropping an ON SELECT rule, that should
-							 * be emitted unchanged as well.
-							 *
-							 * For other object types, we need to extract the
-							 * first part of the DROP which includes the
-							 * object type.  Most of the time this matches
-							 * te->desc, so search for that; however for the
-							 * different kinds of CONSTRAINTs, we know to
-							 * search for hardcoded "DROP CONSTRAINT" instead.
-							 */
-							if (strcmp(te->desc, "DEFAULT") == 0 ||
-								strcmp(te->desc, "DATABASE PROPERTIES") == 0 ||
-								strncmp(dropStmt, "CREATE OR REPLACE VIEW", 22) == 0)
-								appendPQExpBufferStr(ftStmt, dropStmt);
 							else
 							{
-								char		buffer[40];
-								char	   *mark;
-
-								if (strcmp(te->desc, "CONSTRAINT") == 0 ||
-									strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
-									strcmp(te->desc, "FK CONSTRAINT") == 0)
-									strcpy(buffer, "DROP CONSTRAINT");
-								else
-									snprintf(buffer, sizeof(buffer), "DROP %s",
-											 te->desc);
-
-								mark = strstr(dropStmt, buffer);
-
-								if (mark)
-								{
-									*mark = '\0';
-									appendPQExpBuffer(ftStmt, "%s%s IF EXISTS%s",
-													  dropStmt, buffer,
-													  mark + strlen(buffer));
-								}
-								else
-								{
-									/* complain and emit unmodified command */
-									pg_log_warning("could not find where to insert IF EXISTS in statement \"%s\"",
-												   dropStmtOrig);
-									appendPQExpBufferStr(ftStmt, dropStmt);
-								}
+								/* complain and emit unmodified command */
+								pg_log_warning("could not find where to insert IF EXISTS or CASCADE in statement \"%s\"",
+												dropStmtOrig);
+								appendPQExpBufferStr(ftStmt, dropStmt);
 							}
-
 							ahprintf(AH, "%s", ftStmt->data);
 
 							destroyPQExpBuffer(ftStmt);
 							pg_free(dropStmtOrig);
 						}
+
 					}
 				}
 			}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d0ea489614..896f6e7f1e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -375,6 +375,7 @@ main(int argc, char **argv)
 		{"column-inserts", no_argument, &dopt.column_inserts, 1},
 		{"disable-dollar-quoting", no_argument, &dopt.disable_dollar_quoting, 1},
 		{"disable-triggers", no_argument, &dopt.disable_triggers, 1},
+		{"drop-cascade", no_argument, &dopt.drop_cascade, 1},
 		{"enable-row-security", no_argument, &dopt.enable_row_security, 1},
 		{"exclude-table-data", required_argument, NULL, 4},
 		{"extra-float-digits", required_argument, NULL, 8},
@@ -677,6 +678,9 @@ main(int argc, char **argv)
 		exit_nicely(1);
 	}
 
+	if (dopt.drop_cascade && !dopt.outputClean)
+		fatal("option --drop-cascade requires option -c/--clean");
+
 	if (dopt.if_exists && !dopt.outputClean)
 		fatal("option --if-exists requires option -c/--clean");
 
@@ -962,6 +966,7 @@ main(int argc, char **argv)
 	ropt->cparams.pghost = dopt.cparams.pghost ? pg_strdup(dopt.cparams.pghost) : NULL;
 	ropt->cparams.username = dopt.cparams.username ? pg_strdup(dopt.cparams.username) : NULL;
 	ropt->cparams.promptPassword = dopt.cparams.promptPassword;
+	ropt->drop_cascade = dopt.drop_cascade;
 	ropt->dropSchema = dopt.outputClean;
 	ropt->dataOnly = dopt.dataOnly;
 	ropt->schemaOnly = dopt.schemaOnly;
@@ -1063,6 +1068,7 @@ help(const char *progname)
 	printf(_("  --column-inserts             dump data as INSERT commands with column names\n"));
 	printf(_("  --disable-dollar-quoting     disable dollar quoting, use SQL standard quoting\n"));
 	printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
+	printf(_("  --drop-cascade               use CASCADE when dropping objects\n"));
 	printf(_("  --enable-row-security        enable row security (dump only content user has\n"
 			 "                               access to)\n"));
 	printf(_("  --exclude-table-data=PATTERN do NOT dump data for the specified table(s)\n"));
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 007a3d0f9a..4602e196bb 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -69,6 +69,7 @@ static int	binary_upgrade = 0;
 static int	column_inserts = 0;
 static int	disable_dollar_quoting = 0;
 static int	disable_triggers = 0;
+static int	drop_cascade = 0;
 static int	if_exists = 0;
 static int	inserts = 0;
 static int	no_tablespaces = 0;
@@ -128,6 +129,7 @@ main(int argc, char *argv[])
 		{"column-inserts", no_argument, &column_inserts, 1},
 		{"disable-dollar-quoting", no_argument, &disable_dollar_quoting, 1},
 		{"disable-triggers", no_argument, &disable_triggers, 1},
+		{"drop-cascade", no_argument, &drop_cascade, 1},
 		{"exclude-database", required_argument, NULL, 6},
 		{"extra-float-digits", required_argument, NULL, 5},
 		{"if-exists", no_argument, &if_exists, 1},
@@ -360,6 +362,12 @@ main(int argc, char *argv[])
 		exit_nicely(1);
 	}
 
+	if (drop_cascade && !output_clean)
+	{
+		pg_log_error("option --drop-cascade requires option -c/--clean");
+		exit_nicely(1);
+	}
+
 	/* Make sure the user hasn't specified a mix of globals-only options */
 	if (globals_only && roles_only)
 	{
@@ -410,6 +418,8 @@ main(int argc, char *argv[])
 		appendPQExpBufferStr(pgdumpopts, " --disable-dollar-quoting");
 	if (disable_triggers)
 		appendPQExpBufferStr(pgdumpopts, " --disable-triggers");
+	if (drop_cascade)
+		appendPQExpBufferStr(pgdumpopts, " --drop-cascade");
 	if (inserts)
 		appendPQExpBufferStr(pgdumpopts, " --inserts");
 	if (no_tablespaces)
@@ -639,6 +649,7 @@ help(void)
 	printf(_("  --column-inserts             dump data as INSERT commands with column names\n"));
 	printf(_("  --disable-dollar-quoting     disable dollar quoting, use SQL standard quoting\n"));
 	printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
+	printf(_("  --drop-cascade               use CASCADE when dropping objects\n"));
 	printf(_("  --exclude-database=PATTERN   exclude databases whose name matches PATTERN\n"));
 	printf(_("  --extra-float-digits=NUM     override default setting for extra_float_digits\n"));
 	printf(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 589b4aed53..d4636d5664 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -62,6 +62,7 @@ main(int argc, char **argv)
 	Archive    *AH;
 	char	   *inputFileSpec;
 	static int	disable_triggers = 0;
+	static int	drop_cascade = 0;
 	static int	enable_row_security = 0;
 	static int	if_exists = 0;
 	static int	no_data_for_failed_tables = 0;
@@ -108,6 +109,7 @@ main(int argc, char **argv)
 		 * the following options don't have an equivalent short option letter
 		 */
 		{"disable-triggers", no_argument, &disable_triggers, 1},
+		{"drop-cascade", no_argument, &drop_cascade, 1},
 		{"enable-row-security", no_argument, &enable_row_security, 1},
 		{"if-exists", no_argument, &if_exists, 1},
 		{"no-data-for-failed-tables", no_argument, &no_data_for_failed_tables, 1},
@@ -368,6 +370,13 @@ main(int argc, char **argv)
 	}
 
 	opts->disable_triggers = disable_triggers;
+
+	if (drop_cascade && !opts->dropSchema)
+	{
+		pg_log_error("option --drop-cascade requires option -c/--clean");
+		exit_nicely(1);
+	}
+	opts->drop_cascade = drop_cascade;
 	opts->enable_row_security = enable_row_security;
 	opts->noDataForFailedTables = no_data_for_failed_tables;
 	opts->noTablespace = outputNoTablespaces;
@@ -491,6 +500,7 @@ usage(const char *progname)
 	printf(_("  -x, --no-privileges          skip restoration of access privileges (grant/revoke)\n"));
 	printf(_("  -1, --single-transaction     restore as a single transaction\n"));
 	printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
+	printf(_("  --drop-cascade               use CASCADE when dropping objects\n"));
 	printf(_("  --enable-row-security        enable row security\n"));
 	printf(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
 	printf(_("  --no-comments                do not restore comments\n"));
-- 
2.29.2

