>
> > My attempts to test this all got stuck in wait_on_slots(). I haven't
> > looked too closely, but I suspect the issue is that the socket never
> > becomes readable because we don't send a query. If I set
> free_slot->inUse
> > to false before printing the command, it no longer hangs. We probably
> want
> > to create a function in parallel_slot.c to mark slots that we don't
> intend
> > to give a query as idle.
> >
> > Would that be preferable to skipping the creation of extra connections
> for parallel workers? I can see it both ways. On the one hand we want to
> give as true a reflection of "what would happen with these options", and on
> the other hand one could view the creation of extra workers as "real" vs a
> dry run.
> >
> >
>
>
Now with zero hangs and some test cases. I didn't create a function (yet)
as it seemed trivial.
From 396f243e729e2aaaf59cd820def4e093e51033bc Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Mon, 10 Nov 2025 14:33:41 -0500
Subject: [PATCH v2] Add --dry-run to vacuumdb.
This option answers the question "what tables would be affected if I ran
a command using these options" without actually initiating those
actions.
---
src/bin/scripts/t/100_vacuumdb.pl | 12 +++++++++
src/bin/scripts/vacuumdb.c | 6 +++++
src/bin/scripts/vacuuming.c | 42 +++++++++++++++++++++++--------
src/bin/scripts/vacuuming.h | 1 +
doc/src/sgml/ref/vacuumdb.sgml | 11 ++++++++
5 files changed, 61 insertions(+), 11 deletions(-)
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index a16fad593f7..9fef3cbb80f 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -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 --schema');
$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 with missing stats');
$node->issues_sql_like(
[
'vacuumdb', '--analyze-only',
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index e117dac2242..aa0dc366eb0 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -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}
};
@@ -86,6 +87,7 @@ main(int argc, char *argv[])
vacopts.do_truncate = true;
vacopts.process_main = true;
vacopts.process_toast = true;
+ vacopts.dry_run = false;
/* the same for connection parameters */
memset(&cparams, 0, sizeof(cparams));
@@ -213,6 +215,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);
@@ -375,6 +380,7 @@ help(const char *progname)
printf(_(" -Z, --analyze-only only update optimizer statistics; no vacuum\n"));
printf(_(" --analyze-in-stages only update optimizer statistics, in multiple\n"
" stages for faster results; no vacuum\n"));
+ printf(_(" --dry-run do not vacuum/analyze the selected tables, only print\n"));
printf(_(" -?, --help show this help, then exit\n"));
printf(_("\nConnection options:\n"));
printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
diff --git a/src/bin/scripts/vacuuming.c b/src/bin/scripts/vacuuming.c
index f836f21fb03..5d9c07f8715 100644
--- a/src/bin/scripts/vacuuming.c
+++ b/src/bin/scripts/vacuuming.c
@@ -378,13 +378,25 @@ vacuum_one_database(ConnParams *cparams,
prepare_vacuum_command(free_slot->connection, &sql,
vacopts, tabname);
- /*
- * Execute the vacuum. All errors are handled in processQueryResult
- * through ParallelSlotsGetIdle.
- */
- ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
- run_vacuum_command(free_slot->connection, sql.data,
- echo, tabname);
+ if (vacopts->dry_run)
+ {
+ /*
+ * Print the command that we would have run in a real run,
+ * the immediately mark the unused slot as free again.
+ */
+ printf("not executed: %s\n", sql.data);
+ free_slot->inUse = false;
+ }
+ else
+ {
+ /*
+ * Execute the vacuum. All errors are handled in processQueryResult
+ * through ParallelSlotsGetIdle.
+ */
+ ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
+ run_vacuum_command(free_slot->connection, sql.data,
+ echo, tabname);
+ }
cell = cell->next;
} while (cell != NULL);
@@ -407,11 +419,19 @@ vacuum_one_database(ConnParams *cparams,
goto finish;
}
- ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
- run_vacuum_command(free_slot->connection, cmd, echo, NULL);
+ if (vacopts->dry_run)
+ {
+ printf("not executed: %s\n", cmd);
+ free_slot->inUse = false;
+ }
+ else
+ {
+ ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
+ run_vacuum_command(free_slot->connection, cmd, echo, NULL);
- if (!ParallelSlotsWaitCompletion(sa))
- ret = EXIT_FAILURE; /* error already reported by handler */
+ if (!ParallelSlotsWaitCompletion(sa))
+ ret = EXIT_FAILURE; /* error already reported by handler */
+ }
}
finish:
diff --git a/src/bin/scripts/vacuuming.h b/src/bin/scripts/vacuuming.h
index 49f968b32e5..50155239e7e 100644
--- a/src/bin/scripts/vacuuming.h
+++ b/src/bin/scripts/vacuuming.h
@@ -51,6 +51,7 @@ typedef struct vacuumingOptions
bool skip_database_stats;
char *buffer_usage_limit;
bool missing_stats_only;
+ bool dry_run;
} vacuumingOptions;
/* Valid values for vacuumingOptions->objfilter */
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 84c76d7350c..100691b579a 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -171,6 +171,17 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--dry-run</option></term>
+ <listitem>
+ <para>
+ Print but do not execute the vacuum or analyze commands generated.
+ This is useful for testing the effects of various command-line options
+ before actually running the commands.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-e</option></term>
<term><option>--echo</option></term>
base-commit: 6b46669883fac9521c20fe4e2c55ccfbee778591
--
2.51.1