Add option --drop-cascade for pg_dump/restore
Hello,
I'd like to propose adding `--drop-cascade` option for pg_dump/restore
Usecase:
I'd like to be able to restore an old custom format database dump as a
single transaction ( so the current data won't lose if restore fails). The
database has added some new constraints after backup so a CASCADE DROP is
needed.
This allows for restoring an old backup after adding new constraints,
at the risk of losing new data.
There're already some requests for supporting cascade drop:
-
https://dba.stackexchange.com/questions/281384/pg-restore-clean-not-working-because-cascade-drop
-
/messages/by-id/Pine.LNX.4.33.0308281409440.6957-100000@dev2.int.journyx.com
-
/messages/by-id/50EC9574.9060500@encs.concordia.ca
Design & Implementation
Basically I'm following the changes in adding `--if-exists` patch:
https://github.com/postgres/postgres/commit/9067310cc5dd590e36c2c3219dbf3961d7c9f8cb
. pg_dump/restore will inject a CASCADE clause to each DROP command.
The attached patch has been tested on our old backup. I'm happy to get some
feedback.
Attachments:
0001-pg_dump-restore-add-drop-cascade-option.patchapplication/octet-stream; name=0001-pg_dump-restore-add-drop-cascade-option.patchDownload
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
Overall the patch looks good, but I did notice a few small things:
1. In pg_dumpall.c, the section /* Add long options to the pg_dump argument list */, we are now
passing along the --drop-cascade option. However, --clean is not passed in, so
any call to pg_dumpall using --drop-cascade fails a the pg_dump step. You'll note
that --if-exists it not passed along either; because we are dropping the whole database, we don't
need to have pg_dump worry about dropping objects at all. So I think that
--drop-cascade should NOT be passed along from pg_dumpall to pg_dump.
2. I'm not even sure if --drop-cascade makes sense for pg_dumpall, as you cannot cascade global things like databases and roles.
3. In the file pg_backup_archiver.c, the patch does a
stmtEnd = strstr(mark + strlen(buffer), ";");" and then spits
out things "past" the semicolon as the final %s in the appendPQExpBuffer line.
I'm not clear why: are we expecting more things to appear after the semi-colon?
Why not just append a "\n" manually as part of the previous %s?
Cheers,
Greg
The new status of this patch is: Waiting on Author
Hi,
I agree that —drop-cascade does not make sense for pg_dumpall, so I removed them.
are we expecting more things to appear after the semi-colon?
No, I was just trying to “reuse” original statement as much as possible. Append “\n” manually should also do the job, and I’ve updated the patch as you suggests.
Attachments:
0001-pg_dump-restore-add-drop-cascade-option.patchapplication/octet-stream; name=0001-pg_dump-restore-add-drop-cascade-option.patch; x-unix-mode=0644Download
From 1888aa9e12da21db39b6ce0c72be665cd17b0bca 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 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 | 153 ++++++++++++++-------------
src/bin/pg_dump/pg_dump.c | 6 ++
src/bin/pg_dump/pg_restore.c | 10 ++
7 files changed, 127 insertions(+), 74 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..8ce6864b70 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,104 @@ 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();
-
- /*
- * Need to inject IF EXISTS clause after ALTER
- * TABLE part in ALTER TABLE .. DROP statement
- */
- if (strncmp(dropStmt, "ALTER TABLE", 11) == 0)
+ 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)
{
- 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\n",
+ dropStmt, buffer,
+ ropt->if_exists ? "IF EXISTS" : "",
+ mark + strlen(buffer),
+ ropt->drop_cascade ? " CASCADE;" : ";");
}
-
- /*
- * 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_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.31.1
On Fri, Jul 2, 2021 at 12:11 PM Haotian Wu <whtsky@gmail.com> wrote:
Hi,
I agree that —drop-cascade does not make sense for pg_dumpall, so I removed them.
are we expecting more things to appear after the semi-colon?
No, I was just trying to “reuse” original statement as much as possible. Append “\n” manually should also do the job, and I’ve updated the patch as you suggests.
1) This change is not required as it is not supported for pg_dumpall
+++ 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>
+
2) I felt pg_dump will include the cascade option for plain format and
pg_restore will include the cascade option from pg_restore for other
formats. If my understanding is correct, should we document this?
3) This change is not required
destroyPQExpBuffer(ftStmt);
pg_free(dropStmtOrig);
}
+
}
4) Is it possible to add a few tests for this?
Regards,
Vignesh
2) I felt pg_dump will include the cascade option for plain format and
pg_restore will include the cascade option from pg_restore for other
formats. If my understanding is correct, should we document this?
I may not understand it correctly, are you saying
pg_dump will include the cascade option only for plain format, or
pg_dump will enable the cascade option for plain by default?
4) Is it possible to add a few tests for this?
Looks like tests should be added to
`src/bin/pg_dump/t/002_pg_dump.pl`, I'll try to add some.
vignesh C <vignesh21@gmail.com> 于2021年7月13日周二 下午10:23写道:
Show quoted text
On Fri, Jul 2, 2021 at 12:11 PM Haotian Wu <whtsky@gmail.com> wrote:
Hi,
I agree that —drop-cascade does not make sense for pg_dumpall, so I removed them.
are we expecting more things to appear after the semi-colon?
No, I was just trying to “reuse” original statement as much as possible. Append “\n” manually should also do the job, and I’ve updated the patch as you suggests.
1) This change is not required as it is not supported for pg_dumpall +++ 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> +2) I felt pg_dump will include the cascade option for plain format and
pg_restore will include the cascade option from pg_restore for other
formats. If my understanding is correct, should we document this?3) This change is not required
destroyPQExpBuffer(ftStmt);
pg_free(dropStmtOrig);
}
+
}4) Is it possible to add a few tests for this?
Regards,
Vignesh
On Tue, Jul 13, 2021 at 9:16 PM Wu Haotian <whtsky@gmail.com> wrote:
2) I felt pg_dump will include the cascade option for plain format and
pg_restore will include the cascade option from pg_restore for other
formats. If my understanding is correct, should we document this?I may not understand it correctly, are you saying
pg_dump will include the cascade option only for plain format, or
pg_dump will enable the cascade option for plain by default?
pg_dump support plain, custom, tar and directory format, I think,
cascade option will be added by pg_dump only for plain format and for
the other format pg_restore will include the cascade option. Should we
document this somewhere?
4) Is it possible to add a few tests for this?
Looks like tests should be added to
`src/bin/pg_dump/t/002_pg_dump.pl`, I'll try to add some.
Yes, that should be the right place.
Regards,
Vignesh
vignesh C <vignesh21@gmail.com> writes:
On Tue, Jul 13, 2021 at 9:16 PM Wu Haotian <whtsky@gmail.com> wrote:
I may not understand it correctly, are you saying
pg_dump will include the cascade option only for plain format, or
pg_dump will enable the cascade option for plain by default?
pg_dump support plain, custom, tar and directory format, I think,
cascade option will be added by pg_dump only for plain format and for
the other format pg_restore will include the cascade option. Should we
document this somewhere?
That would require pg_restore to try to edit the DROP commands during
restore, which sounds horribly fragile. I'm inclined to think that
supporting this option only during initial dump is safer.
regards, tom lane
On 7/16/21 9:40 AM, Tom Lane wrote:
vignesh C <vignesh21@gmail.com> writes:
On Tue, Jul 13, 2021 at 9:16 PM Wu Haotian <whtsky@gmail.com> wrote:
I may not understand it correctly, are you saying
pg_dump will include the cascade option only for plain format, or
pg_dump will enable the cascade option for plain by default?pg_dump support plain, custom, tar and directory format, I think,
cascade option will be added by pg_dump only for plain format and for
the other format pg_restore will include the cascade option. Should we
document this somewhere?That would require pg_restore to try to edit the DROP commands during
restore, which sounds horribly fragile. I'm inclined to think that
supporting this option only during initial dump is safer.
Maybe, but that would push back the time when you would need to decide
you needed this quite a lot. We could also have pg_dump stash a copy of
the CASCADE variant in the TOC that could be used by pg_restore if
required. I'm not sure if it's worth the trouble and extra space though.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On Fri, Jul 16, 2021 at 9:40 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
That would require pg_restore to try to edit the DROP commands during
restore, which sounds horribly fragile. I'm inclined to think that
supporting this option only during initial dump is safer.
Safer, but not nearly as useful. Maybe see what the OP (Wu Haotian) can
come up with as a first implementation?
Cheers,
Greg
On Tue, Aug 10, 2021 at 10:57 PM Greg Sabino Mullane <htamfids@gmail.com> wrote:
On Fri, Jul 16, 2021 at 9:40 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
That would require pg_restore to try to edit the DROP commands during
restore, which sounds horribly fragile. I'm inclined to think that
supporting this option only during initial dump is safer.Safer, but not nearly as useful. Maybe see what the OP (Wu Haotian) can come up with as a first implementation?
Cheers,
Greg
pg_restore already tries to edit the DROP commands during restore in
order to support `--if-exists`.
supporting this option only during initial dump is safer.
pg_dump & pg_restores use the same function to inject `IF EXISTS` (
and `DROP .. CASCADE` in this patch`).
Supporting this option only during pg_dump may not make it safer, as
the logic is the same.
Hi,
I've updated the patch to remove unnecessary changes and added tests.
On Fri, Jul 16, 2021 at 9:09 PM vignesh C <vignesh21@gmail.com> wrote:
pg_dump support plain, custom, tar and directory format, I think,
cascade option will be added by pg_dump only for plain format and for
the other format pg_restore will include the cascade option. Should we
document this somewhere?
Yes, cascade option relies on `--clean` which only works for plain
format in pg_dump.
Maybe we can add checks like "option --clean requires plain text format"?
If so, should I start a new mail thread for this?
Attachments:
0003-pg_dump-restore-add-drop-cascade-option.patchapplication/octet-stream; name=0003-pg_dump-restore-add-drop-cascade-option.patchDownload
From a7b90d36b64021315c09a15d17f8cf26a6e72fe7 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 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_restore.sgml | 10 ++
src/bin/pg_dump/pg_backup.h | 2 +
src/bin/pg_dump/pg_backup_archiver.c | 157 ++++++++++++++-------------
src/bin/pg_dump/pg_dump.c | 6 +
src/bin/pg_dump/pg_restore.c | 10 ++
src/bin/pg_dump/t/001_basic.pl | 7 +-
src/bin/pg_dump/t/002_pg_dump.pl | 147 +++++++++++++++++--------
8 files changed, 228 insertions(+), 121 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_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..68c38eae49 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,95 +529,103 @@ 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();
-
- /*
- * Need to inject IF EXISTS clause after ALTER
- * TABLE part in ALTER TABLE .. DROP statement
- */
- if (strncmp(dropStmt, "ALTER TABLE", 11) == 0)
+ 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)
{
- appendPQExpBufferStr(ftStmt,
- "ALTER TABLE IF EXISTS");
- dropStmt = dropStmt + 11;
+ char *name = mark + strlen(buffer);
+ *mark = '\0';
+ /*
+ * pg_dump always add CASCADE for TYPE with circular dependencies.
+ * avoid adding duplicated CASCADE.
+ */
+ bool shouldCascade = ropt->drop_cascade && strstr(name, "CASCADE;") == NULL;
+ char *stmtEnd = strstr(name, ";");
+ *stmtEnd = '\0';
+ appendPQExpBuffer(ftStmt, "%s%s%s%s%s\n",
+ dropStmt, buffer,
+ ropt->if_exists ? " IF EXISTS" : "",
+ name,
+ shouldCascade ? " CASCADE;" : ";");
}
-
- /*
- * 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);
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_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"));
diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index 083fb3ad08..e5fa4ce013 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 82;
+use Test::More tests => 84;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -93,6 +93,11 @@ command_fails_like(
qr/\Qpg_dump: error: option --if-exists requires option -c\/--clean\E/,
'pg_dump: option --if-exists requires option -c/--clean');
+command_fails_like(
+ [ 'pg_dump', '--drop-cascade' ],
+ qr/\Qpg_dump: error: option --drop-cascade requires option -c\/--clean\E/,
+ 'pg_dump: option --drop-cascade requires option -c/--clean');
+
command_fails_like(
[ 'pg_dump', '-j3' ],
qr/\Qpg_dump: error: parallel backup only supported by the directory format\E/,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 86113df29c..1f39dc3c4f 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -73,6 +73,29 @@ my %pgdump_runs = (
'postgres',
],
},
+ clean_drop_cascade => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/clean_drop_cascade.sql",
+ '-c',
+ '--drop-cascade',
+ '--encoding=UTF8', # no-op, just tests that option is accepted
+ 'postgres',
+ ],
+ },
+ clean_if_exists_drop_cascade => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/clean_if_exists_drop_cascade.sql",
+ '-c',
+ '--if-exists',
+ '--drop-cascade',
+ '--encoding=UTF8', # no-op, just tests that option is accepted
+ 'postgres',
+ ],
+ },
column_inserts => {
dump_cmd => [
'pg_dump', '--no-sync',
@@ -386,20 +409,22 @@ my %dump_test_schema_runs = (
# Tests which are considered 'full' dumps by pg_dump, but there
# are flags used to exclude specific items (ACLs, blobs, etc).
my %full_runs = (
- binary_upgrade => 1,
- clean => 1,
- clean_if_exists => 1,
- createdb => 1,
- defaults => 1,
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- exclude_test_table_data => 1,
- no_blobs => 1,
- no_owner => 1,
- no_privs => 1,
- pg_dumpall_dbprivs => 1,
- pg_dumpall_exclude => 1,
- schema_only => 1,);
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ clean_drop_cascade => 1,
+ clean_if_exists_drop_cascade => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_dump_test_schema => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_blobs => 1,
+ no_owner => 1,
+ no_privs => 1,
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ schema_only => 1,);
# This is where the actual tests are defined.
my %tests = (
@@ -2676,22 +2701,24 @@ my %tests = (
\QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E
/xm,
like => {
- binary_upgrade => 1,
- clean => 1,
- clean_if_exists => 1,
- createdb => 1,
- defaults => 1,
- exclude_test_table => 1,
- exclude_test_table_data => 1,
- no_blobs => 1,
- no_privs => 1,
- no_owner => 1,
- only_dump_test_schema => 1,
- pg_dumpall_dbprivs => 1,
- pg_dumpall_exclude => 1,
- schema_only => 1,
- section_post_data => 1,
- test_schema_plus_blobs => 1,
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ clean_drop_cascade => 1,
+ clean_if_exists_drop_cascade => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_blobs => 1,
+ no_privs => 1,
+ no_owner => 1,
+ only_dump_test_schema => 1,
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ schema_only => 1,
+ section_post_data => 1,
+ test_schema_plus_blobs => 1,
},
unlike => {
exclude_dump_test_schema => 1,
@@ -2747,22 +2774,24 @@ my %tests = (
\QALTER INDEX dump_test.measurement_pkey ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_pkey\E
/xm,
like => {
- binary_upgrade => 1,
- clean => 1,
- clean_if_exists => 1,
- createdb => 1,
- defaults => 1,
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- exclude_test_table_data => 1,
- no_blobs => 1,
- no_privs => 1,
- no_owner => 1,
- pg_dumpall_dbprivs => 1,
- pg_dumpall_exclude => 1,
- role => 1,
- schema_only => 1,
- section_post_data => 1,
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ clean_drop_cascade => 1,
+ clean_if_exists_drop_cascade => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_dump_test_schema => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_blobs => 1,
+ no_privs => 1,
+ no_owner => 1,
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ role => 1,
+ schema_only => 1,
+ section_post_data => 1,
},
unlike => {
only_dump_test_schema => 1,
@@ -2904,6 +2933,32 @@ my %tests = (
like => { clean_if_exists => 1, },
},
+ 'DROP TYPE int42 CASCADE' => {
+ regexp => qr/^DROP TYPE dump_test.int42 CASCADE;/m,
+ like => { clean => 1, clean_drop_cascade => 1 },
+ },
+ 'DROP FUNCTION trigger_func CASCADE' => {
+ regexp => qr/^
+ \QDROP FUNCTION dump_test.trigger_func() CASCADE;\E
+ /xm,
+ like => { clean_drop_cascade => 1, },
+ },
+
+ 'DROP LANGUAGE pltestlang' => {
+ regexp => qr/^DROP PROCEDURAL LANGUAGE pltestlang CASCADE;/m,
+ like => { clean_drop_cascade => 1, },
+ },
+
+ 'DROP SCHEMA dump_test' => {
+ regexp => qr/^DROP SCHEMA dump_test CASCADE;/m,
+ like => { clean_drop_cascade => 1, },
+ },
+
+ 'DROP TABLE test_table' => {
+ regexp => qr/^DROP TABLE dump_test\.test_table CASCADE;/m,
+ like => { clean_drop_cascade => 1, },
+ },
+
'DROP ROLE regress_dump_test_role' => {
regexp => qr/^
\QDROP ROLE regress_dump_test_role;\E
--
2.31.1
On Wed, Aug 11, 2021 at 10:53 PM Wu Haotian <whtsky@gmail.com> wrote:
Maybe we can add checks like "option --clean requires plain text format"?
If so, should I start a new mail thread for this?
Shrug. To me, that seems related enough it could go into the existing
patch/thread.
Cheers,
Greg
There are already documents for "--clean only works with plain text output",
so adding checks for --clean seems like a breaking change to me.
I've updated the docs to indicate --drop-cascade and --if-exists only
works with plain text output.
Attachments:
0004-pg_dump-restore-add-drop-cascade-option.patchapplication/octet-stream; name=0004-pg_dump-restore-add-drop-cascade-option.patchDownload
From c2f0f3e922a55216a4d35092426c1f65bb218525 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 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 | 22 ++++
doc/src/sgml/ref/pg_restore.sgml | 10 ++
src/bin/pg_dump/pg_backup.h | 2 +
src/bin/pg_dump/pg_backup_archiver.c | 157 ++++++++++++++-------------
src/bin/pg_dump/pg_dump.c | 6 +
src/bin/pg_dump/pg_restore.c | 10 ++
src/bin/pg_dump/t/001_basic.pl | 7 +-
src/bin/pg_dump/t/002_pg_dump.pl | 147 +++++++++++++++++--------
8 files changed, 240 insertions(+), 121 deletions(-)
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 529b167c96..07971e549f 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -732,6 +732,22 @@ 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>
+
+ <para>
+ This option is ignored when emitting an archive (non-text) output
+ file. For the archive formats, you can specify the option when you
+ call <command>pg_restore</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--enable-row-security</option></term>
<listitem>
@@ -791,6 +807,12 @@ PostgreSQL documentation
clause) when cleaning database objects. This option is not valid
unless <option>--clean</option> is also specified.
</para>
+
+ <para>
+ This option is ignored when emitting an archive (non-text) output
+ file. For the archive formats, you can specify the option when you
+ call <command>pg_restore</command>.
+ </para>
</listitem>
</varlistentry>
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..68c38eae49 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,95 +529,103 @@ 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();
-
- /*
- * Need to inject IF EXISTS clause after ALTER
- * TABLE part in ALTER TABLE .. DROP statement
- */
- if (strncmp(dropStmt, "ALTER TABLE", 11) == 0)
+ 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)
{
- appendPQExpBufferStr(ftStmt,
- "ALTER TABLE IF EXISTS");
- dropStmt = dropStmt + 11;
+ char *name = mark + strlen(buffer);
+ *mark = '\0';
+ /*
+ * pg_dump always add CASCADE for TYPE with circular dependencies.
+ * avoid adding duplicated CASCADE.
+ */
+ bool shouldCascade = ropt->drop_cascade && strstr(name, "CASCADE;") == NULL;
+ char *stmtEnd = strstr(name, ";");
+ *stmtEnd = '\0';
+ appendPQExpBuffer(ftStmt, "%s%s%s%s%s\n",
+ dropStmt, buffer,
+ ropt->if_exists ? " IF EXISTS" : "",
+ name,
+ shouldCascade ? " CASCADE;" : ";");
}
-
- /*
- * 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);
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_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"));
diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index 083fb3ad08..e5fa4ce013 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -4,7 +4,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 82;
+use Test::More tests => 84;
my $tempdir = TestLib::tempdir;
my $tempdir_short = TestLib::tempdir_short;
@@ -93,6 +93,11 @@ command_fails_like(
qr/\Qpg_dump: error: option --if-exists requires option -c\/--clean\E/,
'pg_dump: option --if-exists requires option -c/--clean');
+command_fails_like(
+ [ 'pg_dump', '--drop-cascade' ],
+ qr/\Qpg_dump: error: option --drop-cascade requires option -c\/--clean\E/,
+ 'pg_dump: option --drop-cascade requires option -c/--clean');
+
command_fails_like(
[ 'pg_dump', '-j3' ],
qr/\Qpg_dump: error: parallel backup only supported by the directory format\E/,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 86113df29c..1f39dc3c4f 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -73,6 +73,29 @@ my %pgdump_runs = (
'postgres',
],
},
+ clean_drop_cascade => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/clean_drop_cascade.sql",
+ '-c',
+ '--drop-cascade',
+ '--encoding=UTF8', # no-op, just tests that option is accepted
+ 'postgres',
+ ],
+ },
+ clean_if_exists_drop_cascade => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/clean_if_exists_drop_cascade.sql",
+ '-c',
+ '--if-exists',
+ '--drop-cascade',
+ '--encoding=UTF8', # no-op, just tests that option is accepted
+ 'postgres',
+ ],
+ },
column_inserts => {
dump_cmd => [
'pg_dump', '--no-sync',
@@ -386,20 +409,22 @@ my %dump_test_schema_runs = (
# Tests which are considered 'full' dumps by pg_dump, but there
# are flags used to exclude specific items (ACLs, blobs, etc).
my %full_runs = (
- binary_upgrade => 1,
- clean => 1,
- clean_if_exists => 1,
- createdb => 1,
- defaults => 1,
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- exclude_test_table_data => 1,
- no_blobs => 1,
- no_owner => 1,
- no_privs => 1,
- pg_dumpall_dbprivs => 1,
- pg_dumpall_exclude => 1,
- schema_only => 1,);
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ clean_drop_cascade => 1,
+ clean_if_exists_drop_cascade => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_dump_test_schema => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_blobs => 1,
+ no_owner => 1,
+ no_privs => 1,
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ schema_only => 1,);
# This is where the actual tests are defined.
my %tests = (
@@ -2676,22 +2701,24 @@ my %tests = (
\QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E
/xm,
like => {
- binary_upgrade => 1,
- clean => 1,
- clean_if_exists => 1,
- createdb => 1,
- defaults => 1,
- exclude_test_table => 1,
- exclude_test_table_data => 1,
- no_blobs => 1,
- no_privs => 1,
- no_owner => 1,
- only_dump_test_schema => 1,
- pg_dumpall_dbprivs => 1,
- pg_dumpall_exclude => 1,
- schema_only => 1,
- section_post_data => 1,
- test_schema_plus_blobs => 1,
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ clean_drop_cascade => 1,
+ clean_if_exists_drop_cascade => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_blobs => 1,
+ no_privs => 1,
+ no_owner => 1,
+ only_dump_test_schema => 1,
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ schema_only => 1,
+ section_post_data => 1,
+ test_schema_plus_blobs => 1,
},
unlike => {
exclude_dump_test_schema => 1,
@@ -2747,22 +2774,24 @@ my %tests = (
\QALTER INDEX dump_test.measurement_pkey ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_pkey\E
/xm,
like => {
- binary_upgrade => 1,
- clean => 1,
- clean_if_exists => 1,
- createdb => 1,
- defaults => 1,
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- exclude_test_table_data => 1,
- no_blobs => 1,
- no_privs => 1,
- no_owner => 1,
- pg_dumpall_dbprivs => 1,
- pg_dumpall_exclude => 1,
- role => 1,
- schema_only => 1,
- section_post_data => 1,
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ clean_drop_cascade => 1,
+ clean_if_exists_drop_cascade => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_dump_test_schema => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_blobs => 1,
+ no_privs => 1,
+ no_owner => 1,
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ role => 1,
+ schema_only => 1,
+ section_post_data => 1,
},
unlike => {
only_dump_test_schema => 1,
@@ -2904,6 +2933,32 @@ my %tests = (
like => { clean_if_exists => 1, },
},
+ 'DROP TYPE int42 CASCADE' => {
+ regexp => qr/^DROP TYPE dump_test.int42 CASCADE;/m,
+ like => { clean => 1, clean_drop_cascade => 1 },
+ },
+ 'DROP FUNCTION trigger_func CASCADE' => {
+ regexp => qr/^
+ \QDROP FUNCTION dump_test.trigger_func() CASCADE;\E
+ /xm,
+ like => { clean_drop_cascade => 1, },
+ },
+
+ 'DROP LANGUAGE pltestlang' => {
+ regexp => qr/^DROP PROCEDURAL LANGUAGE pltestlang CASCADE;/m,
+ like => { clean_drop_cascade => 1, },
+ },
+
+ 'DROP SCHEMA dump_test' => {
+ regexp => qr/^DROP SCHEMA dump_test CASCADE;/m,
+ like => { clean_drop_cascade => 1, },
+ },
+
+ 'DROP TABLE test_table' => {
+ regexp => qr/^DROP TABLE dump_test\.test_table CASCADE;/m,
+ like => { clean_drop_cascade => 1, },
+ },
+
'DROP ROLE regress_dump_test_role' => {
regexp => qr/^
\QDROP ROLE regress_dump_test_role;\E
--
2.31.1
On 16 Aug 2021, at 08:35, Wu Haotian <whtsky@gmail.com> wrote:
There are already documents for "--clean only works with plain text output",
so adding checks for --clean seems like a breaking change to me.I've updated the docs to indicate --drop-cascade and --if-exists only
works with plain text output.
This patch fails to apply after recent changes to the pg_dump TAP tests.
Please submit a rebased version.
--
Daniel Gustafsson https://vmware.com/
Hi,
here's the rebased patch.
Attachments:
0005-pg_dump-restore-add-drop-cascade-option.patchapplication/octet-stream; name=0005-pg_dump-restore-add-drop-cascade-option.patchDownload
From b170e234c9f474651088b59c966fe21e524562f5 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 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 | 22 ++++
doc/src/sgml/ref/pg_restore.sgml | 10 ++
src/bin/pg_dump/pg_backup.h | 2 +
src/bin/pg_dump/pg_backup_archiver.c | 157 ++++++++++++++-------------
src/bin/pg_dump/pg_dump.c | 6 +
src/bin/pg_dump/pg_restore.c | 10 ++
src/bin/pg_dump/t/001_basic.pl | 7 +-
src/bin/pg_dump/t/002_pg_dump.pl | 153 +++++++++++++++++---------
8 files changed, 243 insertions(+), 124 deletions(-)
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 7682226b99..d333840f84 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -738,6 +738,22 @@ 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>
+
+ <para>
+ This option is ignored when emitting an archive (non-text) output
+ file. For the archive formats, you can specify the option when you
+ call <command>pg_restore</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--enable-row-security</option></term>
<listitem>
@@ -797,6 +813,12 @@ PostgreSQL documentation
clause) when cleaning database objects. This option is not valid
unless <option>--clean</option> is also specified.
</para>
+
+ <para>
+ This option is ignored when emitting an archive (non-text) output
+ file. For the archive formats, you can specify the option when you
+ call <command>pg_restore</command>.
+ </para>
</listitem>
</varlistentry>
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 3c1cd858a8..0f8763b819 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 ee06dc6822..3a0e9d0081 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -186,6 +186,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;
@@ -527,95 +528,103 @@ 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();
-
- /*
- * Need to inject IF EXISTS clause after ALTER
- * TABLE part in ALTER TABLE .. DROP statement
- */
- if (strncmp(dropStmt, "ALTER TABLE", 11) == 0)
+ 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)
{
- appendPQExpBufferStr(ftStmt,
- "ALTER TABLE IF EXISTS");
- dropStmt = dropStmt + 11;
+ char *name = mark + strlen(buffer);
+ *mark = '\0';
+ /*
+ * pg_dump always add CASCADE for TYPE with circular dependencies.
+ * avoid adding duplicated CASCADE.
+ */
+ bool shouldCascade = ropt->drop_cascade && strstr(name, "CASCADE;") == NULL;
+ char *stmtEnd = strstr(name, ";");
+ *stmtEnd = '\0';
+ appendPQExpBuffer(ftStmt, "%s%s%s%s%s\n",
+ dropStmt, buffer,
+ ropt->if_exists ? " IF EXISTS" : "",
+ name,
+ shouldCascade ? " CASCADE;" : ";");
}
-
- /*
- * 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);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 67be849829..5e1249d32e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -377,6 +377,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},
@@ -666,6 +667,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");
@@ -933,6 +937,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;
@@ -1034,6 +1039,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_restore.c b/src/bin/pg_dump/pg_restore.c
index 64aaa80eee..6f0b63d065 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -63,6 +63,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;
@@ -109,6 +110,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},
@@ -356,6 +358,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;
@@ -479,6 +488,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"));
diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index d6731855ed..40e2029c0c 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -7,7 +7,7 @@ use warnings;
use Config;
use PostgresNode;
use TestLib;
-use Test::More tests => 82;
+use Test::More tests => 84;
my $tempdir = TestLib::tempdir;
@@ -95,6 +95,11 @@ command_fails_like(
qr/\Qpg_dump: error: option --if-exists requires option -c\/--clean\E/,
'pg_dump: option --if-exists requires option -c/--clean');
+command_fails_like(
+ [ 'pg_dump', '--drop-cascade' ],
+ qr/\Qpg_dump: error: option --drop-cascade requires option -c\/--clean\E/,
+ 'pg_dump: option --drop-cascade requires option -c/--clean');
+
command_fails_like(
[ 'pg_dump', '-j3' ],
qr/\Qpg_dump: error: parallel backup only supported by the directory format\E/,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index e1b7e31458..b118e15b58 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -75,6 +75,29 @@ my %pgdump_runs = (
'postgres',
],
},
+ clean_drop_cascade => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/clean_drop_cascade.sql",
+ '-c',
+ '--drop-cascade',
+ '--encoding=UTF8', # no-op, just tests that option is accepted
+ 'postgres',
+ ],
+ },
+ clean_if_exists_drop_cascade => {
+ dump_cmd => [
+ 'pg_dump',
+ '--no-sync',
+ "--file=$tempdir/clean_if_exists_drop_cascade.sql",
+ '-c',
+ '--if-exists',
+ '--drop-cascade',
+ '--encoding=UTF8', # no-op, just tests that option is accepted
+ 'postgres',
+ ],
+ },
column_inserts => {
dump_cmd => [
'pg_dump', '--no-sync',
@@ -407,21 +430,23 @@ my %dump_test_schema_runs = (
# Tests which are considered 'full' dumps by pg_dump, but there
# are flags used to exclude specific items (ACLs, blobs, etc).
my %full_runs = (
- binary_upgrade => 1,
- clean => 1,
- clean_if_exists => 1,
- createdb => 1,
- defaults => 1,
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- exclude_test_table_data => 1,
- no_toast_compression => 1,
- no_blobs => 1,
- no_owner => 1,
- no_privs => 1,
- pg_dumpall_dbprivs => 1,
- pg_dumpall_exclude => 1,
- schema_only => 1,);
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ clean_drop_cascade => 1,
+ clean_if_exists_drop_cascade => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_dump_test_schema => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_toast_compression => 1,
+ no_blobs => 1,
+ no_owner => 1,
+ no_privs => 1,
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ schema_only => 1,);
# This is where the actual tests are defined.
my %tests = (
@@ -2844,23 +2869,25 @@ my %tests = (
\QCREATE INDEX measurement_city_id_logdate_idx ON ONLY dump_test.measurement USING\E
/xm,
like => {
- binary_upgrade => 1,
- clean => 1,
- clean_if_exists => 1,
- createdb => 1,
- defaults => 1,
- exclude_test_table => 1,
- exclude_test_table_data => 1,
- no_toast_compression => 1,
- no_blobs => 1,
- no_privs => 1,
- no_owner => 1,
- only_dump_test_schema => 1,
- pg_dumpall_dbprivs => 1,
- pg_dumpall_exclude => 1,
- schema_only => 1,
- section_post_data => 1,
- test_schema_plus_blobs => 1,
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ clean_drop_cascade => 1,
+ clean_if_exists_drop_cascade => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_toast_compression => 1,
+ no_blobs => 1,
+ no_privs => 1,
+ no_owner => 1,
+ only_dump_test_schema => 1,
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ schema_only => 1,
+ section_post_data => 1,
+ test_schema_plus_blobs => 1,
},
unlike => {
exclude_dump_test_schema => 1,
@@ -2916,23 +2943,25 @@ my %tests = (
\QALTER INDEX dump_test.measurement_pkey ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_pkey\E
/xm,
like => {
- binary_upgrade => 1,
- clean => 1,
- clean_if_exists => 1,
- createdb => 1,
- defaults => 1,
- exclude_dump_test_schema => 1,
- exclude_test_table => 1,
- exclude_test_table_data => 1,
- no_toast_compression => 1,
- no_blobs => 1,
- no_privs => 1,
- no_owner => 1,
- pg_dumpall_dbprivs => 1,
- pg_dumpall_exclude => 1,
- role => 1,
- schema_only => 1,
- section_post_data => 1,
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ clean_drop_cascade => 1,
+ clean_if_exists_drop_cascade => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_dump_test_schema => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_toast_compression => 1,
+ no_blobs => 1,
+ no_privs => 1,
+ no_owner => 1,
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ role => 1,
+ schema_only => 1,
+ section_post_data => 1,
},
unlike => {
only_dump_test_schema => 1,
@@ -3074,6 +3103,32 @@ my %tests = (
like => { clean_if_exists => 1, },
},
+ 'DROP TYPE int42 CASCADE' => {
+ regexp => qr/^DROP TYPE dump_test.int42 CASCADE;/m,
+ like => { clean => 1, clean_drop_cascade => 1 },
+ },
+ 'DROP FUNCTION trigger_func CASCADE' => {
+ regexp => qr/^
+ \QDROP FUNCTION dump_test.trigger_func() CASCADE;\E
+ /xm,
+ like => { clean_drop_cascade => 1, },
+ },
+
+ 'DROP LANGUAGE pltestlang' => {
+ regexp => qr/^DROP PROCEDURAL LANGUAGE pltestlang CASCADE;/m,
+ like => { clean_drop_cascade => 1, },
+ },
+
+ 'DROP SCHEMA dump_test' => {
+ regexp => qr/^DROP SCHEMA dump_test CASCADE;/m,
+ like => { clean_drop_cascade => 1, },
+ },
+
+ 'DROP TABLE test_table' => {
+ regexp => qr/^DROP TABLE dump_test\.test_table CASCADE;/m,
+ like => { clean_drop_cascade => 1, },
+ },
+
'DROP ROLE regress_dump_test_role' => {
regexp => qr/^
\QDROP ROLE regress_dump_test_role;\E
--
2.25.1
Wu Haotian <whtsky@gmail.com> writes:
here's the rebased patch.
Looks like it needs rebasing again, probably as a result of our recent
renaming of our Perl test modules.
FWIW, I'd strongly recommend that it's time to pull all that SQL code
hacking out of RestoreArchive and put it in its own function.
regards, tom lane
On 3 Nov 2021, at 20:03, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Wu Haotian <whtsky@gmail.com> writes:
here's the rebased patch.
Looks like it needs rebasing again, probably as a result of our recent
renaming of our Perl test modules.
As this patch hasn't been updated, I'm marking this entry Returned with
Feedback. Please feel free to open a new entry when a rebased patch is
available.
FWIW, I'd strongly recommend that it's time to pull all that SQL code
hacking out of RestoreArchive and put it in its own function.
+1
--
Daniel Gustafsson https://vmware.com/