From 8b2ef45d668722a1728f4f3f2900654f8def1a10 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 27 Dec 2022 16:59:31 -0800
Subject: [PATCH v1 3/3] update datfrozenxid/datminmxid once per database in
 vacuumdb

---
 src/bin/scripts/t/100_vacuumdb.pl | 26 ++++++++--------
 src/bin/scripts/vacuumdb.c        | 52 +++++++++++++++++++++++++++++++
 src/fe_utils/parallel_slot.c      |  4 +++
 3 files changed, 69 insertions(+), 13 deletions(-)

diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index e5343774fe..39eaae4e8e 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -22,15 +22,15 @@ $node->issues_sql_like(
 	'SQL VACUUM run');
 $node->issues_sql_like(
 	[ 'vacuumdb', '-f', 'postgres' ],
-	qr/statement: VACUUM \(FULL\).*;/,
+	qr/statement: VACUUM \(FULL, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb -f');
 $node->issues_sql_like(
 	[ 'vacuumdb', '-F', 'postgres' ],
-	qr/statement: VACUUM \(FREEZE\).*;/,
+	qr/statement: VACUUM \(FREEZE, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb -F');
 $node->issues_sql_like(
 	[ 'vacuumdb', '-zj2', 'postgres' ],
-	qr/statement: VACUUM \(ANALYZE\).*;/,
+	qr/statement: VACUUM \(ANALYZE, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb -zj2');
 $node->issues_sql_like(
 	[ 'vacuumdb', '-Z', 'postgres' ],
@@ -38,11 +38,11 @@ $node->issues_sql_like(
 	'vacuumdb -Z');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--disable-page-skipping', 'postgres' ],
-	qr/statement: VACUUM \(DISABLE_PAGE_SKIPPING\).*;/,
+	qr/statement: VACUUM \(DISABLE_PAGE_SKIPPING, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb --disable-page-skipping');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--skip-locked', 'postgres' ],
-	qr/statement: VACUUM \(SKIP_LOCKED\).*;/,
+	qr/statement: VACUUM \(SKIP_LOCKED, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb --skip-locked');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--skip-locked', '--analyze-only', 'postgres' ],
@@ -53,32 +53,32 @@ $node->command_fails(
 	'--analyze-only and --disable-page-skipping specified together');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--no-index-cleanup', 'postgres' ],
-	qr/statement: VACUUM \(INDEX_CLEANUP FALSE\).*;/,
+	qr/statement: VACUUM \(INDEX_CLEANUP FALSE, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb --no-index-cleanup');
 $node->command_fails(
 	[ 'vacuumdb', '--analyze-only', '--no-index-cleanup', 'postgres' ],
 	'--analyze-only and --no-index-cleanup specified together');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--no-truncate', 'postgres' ],
-	qr/statement: VACUUM \(TRUNCATE FALSE\).*;/,
+	qr/statement: VACUUM \(TRUNCATE FALSE, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb --no-truncate');
 $node->command_fails(
 	[ 'vacuumdb', '--analyze-only', '--no-truncate', 'postgres' ],
 	'--analyze-only and --no-truncate specified together');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--no-process-toast', 'postgres' ],
-	qr/statement: VACUUM \(PROCESS_TOAST FALSE\).*;/,
+	qr/statement: VACUUM \(PROCESS_TOAST FALSE, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb --no-process-toast');
 $node->command_fails(
 	[ 'vacuumdb', '--analyze-only', '--no-process-toast', 'postgres' ],
 	'--analyze-only and --no-process-toast specified together');
 $node->issues_sql_like(
 	[ 'vacuumdb', '-P', 2, 'postgres' ],
-	qr/statement: VACUUM \(PARALLEL 2\).*;/,
+	qr/statement: VACUUM \(PARALLEL 2, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb -P 2');
 $node->issues_sql_like(
 	[ 'vacuumdb', '-P', 0, 'postgres' ],
-	qr/statement: VACUUM \(PARALLEL 0\).*;/,
+	qr/statement: VACUUM \(PARALLEL 0, UPDATE_DATFROZENXID FALSE\).*;/,
 	'vacuumdb -P 0');
 $node->command_ok([qw(vacuumdb -Z --table=pg_am dbname=template1)],
 	'vacuumdb with connection string');
@@ -119,7 +119,7 @@ $node->command_fails([ 'vacuumdb', '-P', -1, 'postgres' ],
 	'negative parallel degree');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--analyze', '--table', 'vactable(a, b)', 'postgres' ],
-	qr/statement: VACUUM \(ANALYZE\) public.vactable\(a, b\);/,
+	qr/statement: VACUUM \(ANALYZE, UPDATE_DATFROZENXID FALSE\) public.vactable\(a, b\);/,
 	'vacuumdb --analyze with complete column list');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--analyze-only', '--table', 'vactable(b)', 'postgres' ],
@@ -150,11 +150,11 @@ $node->issues_sql_like(
 	'vacuumdb --table --min-xid-age');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--schema', '"Foo"', 'postgres' ],
-	qr/VACUUM "Foo".bar/,
+	qr/VACUUM \(UPDATE_DATFROZENXID FALSE\) "Foo".bar/,
 	'vacuumdb --schema');
 $node->issues_sql_like(
 	[ 'vacuumdb', '--exclude-schema', '"Foo"', 'postgres' ],
-	qr/(?:(?!VACUUM "Foo".bar).)*/,
+	qr/(?:(?!VACUUM \(UPDATE_DATFROZENXID FALSE\) "Foo".bar).)*/,
 	'vacuumdb --exclude-schema');
 $node->command_fails_like(
 	[ 'vacuumdb', '-N', 'pg_catalog', '-t', 'pg_class', 'postgres', ],
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 272e37d290..3ba1a37049 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -80,6 +80,8 @@ static void run_vacuum_command(PGconn *conn, const char *sql, bool echo,
 
 static void help(const char *progname);
 
+static bool UpdateDatfrozenxidHandler(PGresult *res, PGconn *conn, void *context);
+
 void check_objfilter(void);
 
 /* For analyze-in-stages mode */
@@ -791,6 +793,29 @@ vacuum_one_database(ConnParams *cparams,
 
 	if (!ParallelSlotsWaitCompletion(sa))
 		failed = true;
+	else if (!vacopts->analyze_only && PQserverVersion(conn) >= 160000)
+	{
+		/* v16 and later updates datfrozenxid/datminmxid at the end */
+		const char *cmd = "SELECT pg_catalog.pg_update_datfrozenxid()";
+		ParallelSlot *free_slot = ParallelSlotsGetIdle(sa, NULL);
+
+		if (!free_slot)
+		{
+			failed = true;
+			goto finish;
+		}
+
+		ParallelSlotSetHandler(free_slot, UpdateDatfrozenxidHandler, NULL);
+
+		if (echo)
+			printf("%s\n", cmd);
+
+		if (PQsendQuery(free_slot->connection, cmd) != 1)
+			pg_log_error("\"pg_update_datfrozenxid()\" failed: %s",
+						 PQerrorMessage(free_slot->connection));
+
+		failed = !ParallelSlotsWaitCompletion(sa);
+	}
 
 finish:
 	ParallelSlotsTerminate(sa);
@@ -802,6 +827,27 @@ finish:
 		exit(1);
 }
 
+/*
+ * UpdateDatfrozenxidHandler
+ *
+ * ParallelSlotResultHandler for result of pg_update_datfrozenxid().  Requires
+ * that the result status is PGRES_TUPLES_OK.  Otherwise, logs an error and
+ * terminates further processing.
+ *
+ * res: PGresult from the query executed on the slot's connection
+ * conn: connection belonging to the slot
+ * context: unused
+ */
+static bool
+UpdateDatfrozenxidHandler(PGresult *res, PGconn *conn, void *context)
+{
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+		pg_log_error("\"pg_update_datfrozenxid()\" failed: %s",
+					 PQerrorMessage(conn));
+
+	return (PQresultStatus(res) == PGRES_TUPLES_OK);
+}
+
 /*
  * Vacuum/analyze all connectable databases.
  *
@@ -992,6 +1038,12 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
 								  vacopts->parallel_workers);
 				sep = comma;
 			}
+			if (serverVersion >= 160000)
+			{
+				/* v16 and later calls pg_update_datfrozenxid() at the end */
+				appendPQExpBuffer(sql, "%sUPDATE_DATFROZENXID FALSE", sep);
+				sep = comma;
+			}
 			if (sep != paren)
 				appendPQExpBufferChar(sql, ')');
 		}
diff --git a/src/fe_utils/parallel_slot.c b/src/fe_utils/parallel_slot.c
index 767256757f..aed9e40608 100644
--- a/src/fe_utils/parallel_slot.c
+++ b/src/fe_utils/parallel_slot.c
@@ -475,6 +475,10 @@ ParallelSlotsWaitCompletion(ParallelSlotArray *sa)
 			continue;
 		if (!consumeQueryResult(&sa->slots[i]))
 			return false;
+
+		/* mark slot as idle so it can be reused */
+		sa->slots[i].inUse = false;
+		ParallelSlotClearHandler(&sa->slots[i]);
 	}
 
 	return true;
-- 
2.25.1

