pg_dump et al: Add --if-exists option
authorAlvaro Herrera
Mon, 3 Mar 2014 18:02:18 +0000 (15:02 -0300)
committerAlvaro Herrera
Mon, 3 Mar 2014 18:02:18 +0000 (15:02 -0300)
This option makes pg_dump, pg_dumpall and pg_restore inject an IF EXISTS
clause to each DROP command they emit.  (In pg_dumpall, the clause is
not added to individual objects drops, but rather to the CREATE DATABASE
commands, as well as CREATE ROLE and CREATE TABLESPACE.)

This allows for a better user dump experience when using --clean in case
some objects do not already exist.  Per bug #7873 by Dave Rolsky.

Author: Pavel Stěhule
Reviewed-by: Jeevan Chalke, Álvaro Herrera, Josh Kupershmidt
doc/src/sgml/ref/pg_dump.sgml
doc/src/sgml/ref/pg_dumpall.sgml
doc/src/sgml/ref/pg_restore.sgml
src/bin/pg_dump/pg_backup.h
src/bin/pg_dump/pg_backup_archiver.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dumpall.c
src/bin/pg_dump/pg_restore.c

index 40c69f05421ecadeb0a0540b6773e00b78921294..1f0d4ded32e36988e00b9d8ce6f8085ebad60de1 100644 (file)
@@ -145,7 +145,8 @@ PostgreSQL documentation
        
         Output commands to clean (drop)
         database objects prior to outputting the commands for creating them.
-        (Restore might generate some harmless error messages, if any objects
+        (Unless 
+        restore might generate some harmless error messages, if any objects
         were not present in the destination database.)
        
 
@@ -649,6 +650,17 @@ PostgreSQL documentation
       
      
 
+     
+      
+      
+       
+        Use conditional commands (i.e. add an IF EXISTS
+        clause) when cleaning database objects.  This option is not valid
+        unless 
+       
+      
+     
+
      
       
       
index f33793985f9569762cf40c466b6acd8228f3a3d8..fcf5f77a6dbfb047b37f389e1b0e7e23797c08ae 100644 (file)
@@ -300,6 +300,17 @@ PostgreSQL documentation
       
      
 
+     
+      
+      
+       
+        Use conditional commands (i.e. add an IF EXISTS
+        clause) to clean databases and other objects.  This option is not valid
+        unless 
+       
+      
+     
+
      
       
       
index cd60b2558a4e975eac1e1c0dce70d7ce2b58977c..4bc30ce679b9c87d05c3872def83773f7b3c8416 100644 (file)
       
        
         Clean (drop) database objects before recreating them.
-        (This might generate some harmless error messages, if any objects
+        (Unless 
+        this might generate some harmless error messages, if any objects
         were not present in the destination database.)
        
       
       
      
 
+     
+      
+      
+       
+        Use conditional commands (i.e. add an IF EXISTS
+        clause) when cleaning database objects.  This option is not valid
+        unless 
+       
+      
+     
+
      
       
       
index 6927968de0a0d7e087d14f05089111c90403f341..83f7216d50e6a66d557854a9f213325d16f703e1 100644 (file)
@@ -113,6 +113,7 @@ typedef struct _restoreOptions
    char       *superuser;      /* Username to use as superuser */
    char       *use_role;       /* Issue SET ROLE to this */
    int         dropSchema;
+   int         if_exists;
    const char *filename;
    int         dataOnly;
    int         schemaOnly;
index 46699a2d1474c91ee58b4612a53be5f6c07707be..946454019bd533471275b5e556b941b0cdc11838 100644 (file)
@@ -413,8 +413,77 @@ RestoreArchive(Archive *AHX)
                /* Select owner and schema as necessary */
                _becomeOwner(AH, te);
                _selectOutputSchema(AH, te->namespace);
-               /* Drop it */
-               ahprintf(AH, "%s", te->dropStmt);
+
+               /*
+                * Now emit the DROP command, if the object has one.  Note we
+                * don't necessarily emit it verbatim; at this point we add an
+                * appropriate IF EXISTS clause, if the user requested it.
+                */
+               if (*te->dropStmt != '\0')
+               {
+                   if (!ropt->if_exists)
+                   {
+                       /* No --if-exists?  Then just use the original */
+                       ahprintf(AH, "%s", te->dropStmt);
+                   }
+                   else
+                   {
+                       char        buffer[40];
+                       char       *mark;
+                       char       *dropStmt = pg_strdup(te->dropStmt);
+                       char       *dropStmtPtr = 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)
+                       {
+                           appendPQExpBuffer(ftStmt,
+                                             "ALTER TABLE IF EXISTS");
+                           dropStmt = dropStmt + 11;
+                       }
+
+                       /*
+                        * ALTER TABLE..ALTER COLUMN..DROP DEFAULT does not
+                        * support the IF EXISTS clause, and therefore we
+                        * simply emit the original command for such objects.
+                        * For other objects, 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)
+                           appendPQExpBuffer(ftStmt, "%s", dropStmt);
+                       else
+                       {
+                           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);
+                           Assert(mark != NULL);
+
+                           *mark = '\0';
+                           appendPQExpBuffer(ftStmt, "%s%s IF EXISTS%s",
+                                             dropStmt, buffer,
+                                             mark + strlen(buffer));
+                       }
+
+                       ahprintf(AH, "%s", ftStmt->data);
+
+                       destroyPQExpBuffer(ftStmt);
+
+                       pg_free(dropStmtPtr);
+                   }
+               }
            }
        }
 
index 770e97dce971ea26cb4fc6e0cf1d11a45ba02723..f5a6bbb723e1f3185a2644a24c65d3c34417bcd8 100644 (file)
@@ -132,6 +132,7 @@ static int  binary_upgrade = 0;
 static int disable_dollar_quoting = 0;
 static int dump_inserts = 0;
 static int column_inserts = 0;
+static int if_exists = 0;
 static int no_security_labels = 0;
 static int no_synchronized_snapshots = 0;
 static int no_unlogged_table_data = 0;
@@ -345,6 +346,7 @@ main(int argc, char **argv)
        {"disable-dollar-quoting", no_argument, &disable_dollar_quoting, 1},
        {"disable-triggers", no_argument, &disable_triggers, 1},
        {"exclude-table-data", required_argument, NULL, 4},
+       {"if-exists", no_argument, &if_exists, 1},
        {"inserts", no_argument, &dump_inserts, 1},
        {"lock-wait-timeout", required_argument, NULL, 2},
        {"no-tablespaces", no_argument, &outputNoTablespaces, 1},
@@ -573,6 +575,9 @@ main(int argc, char **argv)
        exit_nicely(1);
    }
 
+   if (if_exists && !outputClean)
+       exit_horribly(NULL, "option --if-exists requires -c/--clean option\n");
+
    /* Identify archive format to emit */
    archiveFormat = parseArchiveFormat(format, &archiveMode);
 
@@ -805,6 +810,7 @@ main(int argc, char **argv)
    ropt->dropSchema = outputClean;
    ropt->dataOnly = dataOnly;
    ropt->schemaOnly = schemaOnly;
+   ropt->if_exists = if_exists;
    ropt->dumpSections = dumpSections;
    ropt->aclsSkip = aclsSkip;
    ropt->superuser = outputSuperuser;
@@ -886,6 +892,7 @@ help(const char *progname)
    printf(_("  --disable-dollar-quoting     disable dollar quoting, use SQL standard quoting\n"));
    printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
    printf(_("  --exclude-table-data=TABLE   do NOT dump data for the named table(s)\n"));
+   printf(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
    printf(_("  --inserts                    dump data as INSERT commands, rather than COPY\n"));
    printf(_("  --no-security-labels         do not dump security label assignments\n"));
    printf(_("  --no-synchronized-snapshots  do not use synchronized snapshots in parallel jobs\n"));
index 193c1a0c389a941b2e9a240eaaac39ba859fa999..f7c610deeb7001cf9c97595e9e2401cafaa7a645 100644 (file)
@@ -73,6 +73,7 @@ static int    binary_upgrade = 0;
 static int column_inserts = 0;
 static int disable_dollar_quoting = 0;
 static int disable_triggers = 0;
+static int if_exists = 0;
 static int inserts = 0;
 static int no_tablespaces = 0;
 static int use_setsessauth = 0;
@@ -119,6 +120,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},
+       {"if-exists", no_argument, &if_exists, 1},
        {"inserts", no_argument, &inserts, 1},
        {"lock-wait-timeout", required_argument, NULL, 2},
        {"no-tablespaces", no_argument, &no_tablespaces, 1},
@@ -334,6 +336,13 @@ main(int argc, char *argv[])
        exit_nicely(1);
    }
 
+   if (if_exists && !output_clean)
+   {
+       fprintf(stderr, _("%s: option --if-exists requires -c/--clean option\n"),
+               progname);
+       exit_nicely(1);
+   }
+
    if (roles_only && tablespaces_only)
    {
        fprintf(stderr, _("%s: options -r/--roles-only and -t/--tablespaces-only cannot be used together\n"),
@@ -564,6 +573,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(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
    printf(_("  --inserts                    dump data as INSERT commands, rather than COPY\n"));
    printf(_("  --no-security-labels         do not dump security label assignments\n"));
    printf(_("  --no-tablespaces             do not dump tablespace assignments\n"));
@@ -624,7 +634,9 @@ dropRoles(PGconn *conn)
 
        rolename = PQgetvalue(res, i, i_rolname);
 
-       fprintf(OPF, "DROP ROLE %s;\n", fmtId(rolename));
+       fprintf(OPF, "DROP ROLE %s%s;\n",
+               if_exists ? "IF EXISTS " : "",
+               fmtId(rolename));
    }
 
    PQclear(res);
@@ -994,7 +1006,9 @@ dropTablespaces(PGconn *conn)
    {
        char       *spcname = PQgetvalue(res, i, 0);
 
-       fprintf(OPF, "DROP TABLESPACE %s;\n", fmtId(spcname));
+       fprintf(OPF, "DROP TABLESPACE %s%s;\n",
+               if_exists ? "IF EXISTS " : "",
+               fmtId(spcname));
    }
 
    PQclear(res);
@@ -1148,7 +1162,9 @@ dropDBs(PGconn *conn)
        if (strcmp(dbname, "template1") != 0 &&
            strcmp(dbname, "postgres") != 0)
        {
-           fprintf(OPF, "DROP DATABASE %s;\n", fmtId(dbname));
+           fprintf(OPF, "DROP DATABASE %s%s;\n",
+                   if_exists ? "IF EXISTS " : "",
+                   fmtId(dbname));
        }
    }
 
index df9477b69fa97a02c000d1e50a21435f1a0d0653..f7f3f51ed3550613d893d2e55e220fa4e9281009 100644 (file)
@@ -70,6 +70,7 @@ main(int argc, char **argv)
    Archive    *AH;
    char       *inputFileSpec;
    static int  disable_triggers = 0;
+   static int  if_exists = 0;
    static int  no_data_for_failed_tables = 0;
    static int  outputNoTablespaces = 0;
    static int  use_setsessauth = 0;
@@ -110,6 +111,7 @@ main(int argc, char **argv)
         * the following options don't have an equivalent short option letter
         */
        {"disable-triggers", no_argument, &disable_triggers, 1},
+       {"if-exists", no_argument, &if_exists, 1},
        {"no-data-for-failed-tables", no_argument, &no_data_for_failed_tables, 1},
        {"no-tablespaces", no_argument, &outputNoTablespaces, 1},
        {"role", required_argument, NULL, 2},
@@ -336,6 +338,14 @@ main(int argc, char **argv)
    opts->use_setsessauth = use_setsessauth;
    opts->no_security_labels = no_security_labels;
 
+   if (if_exists && !opts->dropSchema)
+   {
+       fprintf(stderr, _("%s: option --if-exists requires -c/--clean option\n"),
+               progname);
+       exit_nicely(1);
+   }
+   opts->if_exists = if_exists;
+
    if (opts->formatName)
    {
        switch (opts->formatName[0])
@@ -450,6 +460,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(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
    printf(_("  --no-data-for-failed-tables  do not restore data of tables that could not be\n"
             "                               created\n"));
    printf(_("  --no-security-labels         do not restore security labels\n"));