vacuumdb: Add --dry-run.
authorNathan Bossart <[email protected]>
Tue, 9 Dec 2025 19:34:22 +0000 (13:34 -0600)
committerNathan Bossart <[email protected]>
Tue, 9 Dec 2025 19:34:22 +0000 (13:34 -0600)
This new option instructs vacuumdb to print, but not execute, the
VACUUM and ANALYZE commands that would've been sent to the server.

Author: Corey Huinker <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Discussion: https://postgr.es/m/CADkLM%3DckHkX7Of5SrK7g0LokPUwJ%3Dkk8JU1GXGF5pZ1eBVr0%3DQ%40mail.gmail.com

doc/src/sgml/ref/vacuumdb.sgml
src/bin/scripts/t/100_vacuumdb.pl
src/bin/scripts/vacuumdb.c
src/bin/scripts/vacuuming.c
src/bin/scripts/vacuuming.h

index 84c76d7350c83d6bf155cee60ec97b5ca3d5f9a3..508c8dfa146414e43a9b1226a80123a8b9841ab7 100644 (file)
@@ -171,6 +171,16 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--dry-run</option></term>
+      <listitem>
+       <para>
+        Print, but do not execute, the vacuum and analyze commands that would
+        have been sent to the server.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-e</option></term>
       <term><option>--echo</option></term>
index a16fad593f7ca73d5bcb9aef4ff1431c752d2cb7..fb2fecdd3c6f2d3d90932c9061d1b1538b0a54f6 100644 (file)
@@ -169,6 +169,10 @@ $node->issues_sql_like(
    [ 'vacuumdb', '--schema' => '"Foo"', 'postgres' ],
    qr/VACUUM \(SKIP_DATABASE_STATS\) "Foo".bar/,
    'vacuumdb --schema');
+$node->issues_sql_unlike(
+   [ 'vacuumdb', '--schema' => '"Foo"', 'postgres', '--dry-run' ],
+   qr/VACUUM \(SKIP_DATABASE_STATS\) "Foo".bar/,
+   'vacuumdb --dry-run');
 $node->issues_sql_like(
    [ 'vacuumdb', '--schema' => '"Foo"', '--schema' => '"Bar"', 'postgres' ],
    qr/VACUUM\ \(SKIP_DATABASE_STATS\)\ "Foo".bar
@@ -241,6 +245,14 @@ $node->safe_psql('postgres', q|
   CREATE TABLE regression_vacuumdb_test AS select generate_series(1, 10) a, generate_series(2, 11) b;
   ALTER TABLE regression_vacuumdb_test ADD COLUMN c INT GENERATED ALWAYS AS (a + b);
 |);
+$node->issues_sql_unlike(
+   [
+       'vacuumdb', '--analyze-only', '--dry-run',
+       '--missing-stats-only', '-t',
+       'regression_vacuumdb_test', 'postgres'
+   ],
+   qr/statement:\ ANALYZE/sx,
+   '--missing-stats-only --dry-run');
 $node->issues_sql_like(
    [
        'vacuumdb', '--analyze-only',
index 6783c84363714a9878d7b7a677cebfed6ea288c4..0bc443be34886d48264854dd46f39033c78410d7 100644 (file)
@@ -59,6 +59,7 @@ main(int argc, char *argv[])
        {"no-process-main", no_argument, NULL, 12},
        {"buffer-usage-limit", required_argument, NULL, 13},
        {"missing-stats-only", no_argument, NULL, 14},
+       {"dry-run", no_argument, NULL, 15},
        {NULL, 0, NULL, 0}
    };
 
@@ -207,6 +208,9 @@ main(int argc, char *argv[])
            case 14:
                vacopts.missing_stats_only = true;
                break;
+           case 15:
+               vacopts.dry_run = true;
+               break;
            default:
                /* getopt_long already emitted a complaint */
                pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -303,6 +307,10 @@ main(int argc, char *argv[])
        pg_fatal("cannot use the \"%s\" option without \"%s\" or \"%s\"",
                 "missing-stats-only", "analyze-only", "analyze-in-stages");
 
+   if (vacopts.dry_run && !vacopts.quiet)
+       pg_log_info("Executing in dry-run mode.\n"
+                   "No commands will be sent to the server.");
+
    ret = vacuuming_main(&cparams, dbname, maintenance_db, &vacopts,
                         &objects, tbl_count,
                         concurrentCons,
@@ -345,6 +353,7 @@ help(const char *progname)
    printf(_("      --buffer-usage-limit=SIZE   size of ring buffer used for vacuum\n"));
    printf(_("  -d, --dbname=DBNAME             database to vacuum\n"));
    printf(_("      --disable-page-skipping     disable all page-skipping behavior\n"));
+   printf(_("      --dry-run                   show the commands that would be sent to the server\n"));
    printf(_("  -e, --echo                      show the commands being sent to the server\n"));
    printf(_("  -f, --full                      do full vacuuming\n"));
    printf(_("  -F, --freeze                    freeze row transaction information\n"));
index 4f665c9f7d618a8254d447bc1075a5a2465bc677..5d2d8a649612543e382f06d109e23e90a9f494c2 100644 (file)
@@ -42,8 +42,9 @@ static SimpleStringList *retrieve_objects(PGconn *conn,
 static void free_retrieved_objects(SimpleStringList *list);
 static void prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
                                   vacuumingOptions *vacopts, const char *table);
-static void run_vacuum_command(PGconn *conn, vacuumingOptions *vacopts,
-                              const char *sql, const char *table);
+static void run_vacuum_command(ParallelSlot *free_slot,
+                              vacuumingOptions *vacopts, const char *sql,
+                              const char *table);
 
 /*
  * Executes vacuum/analyze as indicated.  Returns 0 if the plan is carried
@@ -340,7 +341,11 @@ vacuum_one_database(ConnParams *cparams,
    if (vacopts->mode == MODE_ANALYZE_IN_STAGES)
    {
        initcmd = stage_commands[stage];
-       executeCommand(conn, initcmd, vacopts->echo);
+
+       if (vacopts->dry_run)
+           printf("%s\n", initcmd);
+       else
+           executeCommand(conn, initcmd, vacopts->echo);
    }
    else
        initcmd = NULL;
@@ -383,7 +388,7 @@ vacuum_one_database(ConnParams *cparams,
         * through ParallelSlotsGetIdle.
         */
        ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
-       run_vacuum_command(free_slot->connection, vacopts, sql.data, tabname);
+       run_vacuum_command(free_slot, vacopts, sql.data, tabname);
 
        cell = cell->next;
    } while (cell != NULL);
@@ -407,7 +412,7 @@ vacuum_one_database(ConnParams *cparams,
        }
 
        ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
-       run_vacuum_command(free_slot->connection, vacopts, cmd, NULL);
+       run_vacuum_command(free_slot, vacopts, cmd, NULL);
 
        if (!ParallelSlotsWaitCompletion(sa))
            ret = EXIT_FAILURE; /* error already reported by handler */
@@ -995,20 +1000,25 @@ prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
 
 /*
  * Send a vacuum/analyze command to the server, returning after sending the
- * command.
+ * command.  If dry_run is true, the command is printed but not sent to the
+ * server.
  *
  * Any errors during command execution are reported to stderr.
  */
 static void
-run_vacuum_command(PGconn *conn, vacuumingOptions *vacopts,
+run_vacuum_command(ParallelSlot *free_slot, vacuumingOptions *vacopts,
                   const char *sql, const char *table)
 {
-   bool        status;
+   bool        status = true;
+   PGconn     *conn = free_slot->connection;
 
-   if (vacopts->echo)
+   if (vacopts->echo || vacopts->dry_run)
        printf("%s\n", sql);
 
-   status = PQsendQuery(conn, sql) == 1;
+   if (vacopts->dry_run)
+       ParallelSlotSetIdle(free_slot);
+   else
+       status = PQsendQuery(conn, sql) == 1;
 
    if (!status)
    {
index 90db4fa1a640ea3d8abb5487a2cce14740751d75..586b6caa3d67109e3e5097f4e85466ed0e2ad9d7 100644 (file)
@@ -53,6 +53,7 @@ typedef struct vacuumingOptions
    bool        missing_stats_only;
    bool        echo;
    bool        quiet;
+   bool        dry_run;
 } vacuumingOptions;
 
 /* Valid values for vacuumingOptions->objfilter */