From 742f23c611d896e386a865fb1a80b151471351fa Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 1 Feb 2022 22:28:56 -0600
Subject: [PATCH 3/3] VACUUM(EMERGENCY): sort tables by age(m/xid)

---
 src/backend/commands/vacuum.c        | 38 +++++++++++++++++++++++++---
 src/backend/nodes/copyfuncs.c        |  1 +
 src/backend/nodes/equalfuncs.c       |  1 +
 src/backend/nodes/makefuncs.c        |  3 ++-
 src/backend/parser/gram.y            |  2 +-
 src/backend/postmaster/autovacuum.c  |  2 +-
 src/include/nodes/makefuncs.h        |  3 ++-
 src/include/nodes/parsenodes.h       |  1 +
 src/test/regress/expected/vacuum.out |  7 +++++
 src/test/regress/sql/vacuum.sql      |  4 +++
 10 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 6bdce7d1373..2fa1911417c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -224,7 +224,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 		(freeze ? VACOPT_FREEZE : 0) |
 		(full ? VACOPT_FULL : 0) |
 		(disable_page_skipping ? VACOPT_DISABLE_PAGE_SKIPPING : 0) |
-		(process_toast ? VACOPT_PROCESS_TOAST : 0) |
+		(process_toast && !emergency ? VACOPT_PROCESS_TOAST : 0) |
 		(emergency ? VACOPT_EMERGENCY : 0);
 
 	/* sanity checks on options */
@@ -291,8 +291,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 		// params.min_xid_age = 1000 * 1000 * 1000;
 		// params.min_mxid_age = 1000 * 1000 * 1000;
 		// FIXME to speed up testing
-		params.min_xid_age = 1000 * 1000;
-		params.min_mxid_age = 1000 * 1000;
+		params.min_xid_age = 1000 ;
+		params.min_mxid_age = 1000 ;
 	}
 
 	/*
@@ -889,6 +889,7 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
 			oldcontext = MemoryContextSwitchTo(vac_context);
 			vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation,
 														  relid,
+														  0,
 														  vrel->va_cols));
 			MemoryContextSwitchTo(oldcontext);
 		}
@@ -926,6 +927,7 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
 				oldcontext = MemoryContextSwitchTo(vac_context);
 				vacrels = lappend(vacrels, makeVacuumRelation(NULL,
 															  part_oid,
+															  0,
 															  vrel->va_cols));
 				MemoryContextSwitchTo(oldcontext);
 			}
@@ -948,6 +950,26 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
 	return vacrels;
 }
 
+/*
+ * Helper function for qsort()
+ *
+ * The result is in order of decreasing age.
+ */
+static int
+compare_xidage(const void *a, const void *b)
+{
+	const ListCell *alc = a;
+	const ListCell *blc = b;
+	VacuumRelation *aa = (VacuumRelation *) lfirst(alc);
+	VacuumRelation *bb = (VacuumRelation *) lfirst(blc);
+
+	if (aa->age < bb->age)
+		return +1;
+	if (aa->age > bb->age)
+		return -1;
+	return 0;
+}
+
 /*
  * Construct a list of VacuumRelations for all vacuumable rels in
  * the current database.  The list is built in vac_context.
@@ -971,6 +993,7 @@ get_all_vacuum_rels(VacuumParams *params)
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		MemoryContext oldcontext;
 		Oid			relid = classForm->oid;
+		TransactionId	table_age = 0;
 
 		/* check permissions of relation */
 		if (!vacuum_is_relation_owner(relid, classForm, params->options))
@@ -993,6 +1016,7 @@ get_all_vacuum_rels(VacuumParams *params)
 
 			table_xid_age = DirectFunctionCall1(xid_age, classForm->relfrozenxid);
 			table_mxid_age = DirectFunctionCall1(mxid_age, classForm->relminmxid);
+			table_age = Max(table_xid_age, table_mxid_age);
 
 			if ((table_xid_age < params->min_xid_age) &&
 				(table_mxid_age < params->min_mxid_age))
@@ -1016,6 +1040,7 @@ get_all_vacuum_rels(VacuumParams *params)
 		oldcontext = MemoryContextSwitchTo(vac_context);
 		vacrels = lappend(vacrels, makeVacuumRelation(NULL,
 													  relid,
+													  table_age,
 													  NIL));
 		MemoryContextSwitchTo(oldcontext);
 	}
@@ -1023,6 +1048,13 @@ get_all_vacuum_rels(VacuumParams *params)
 	table_endscan(scan);
 	table_close(pgclass, AccessShareLock);
 
+	if ((params->options & VACOPT_EMERGENCY) && vacrels != NIL)
+	{
+		/* Sort the list in order of deceasing XID age */
+		qsort(vacrels->elements, list_length(vacrels), sizeof(ListCell),
+				compare_xidage);
+	}
+
 	return vacrels;
 }
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90b5da51c95..50dab1bb21b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4100,6 +4100,7 @@ _copyVacuumRelation(const VacuumRelation *from)
 
 	COPY_NODE_FIELD(relation);
 	COPY_SCALAR_FIELD(oid);
+	COPY_SCALAR_FIELD(age);
 	COPY_NODE_FIELD(va_cols);
 
 	return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 06345da3ba8..3c1adcaf292 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1768,6 +1768,7 @@ _equalVacuumRelation(const VacuumRelation *a, const VacuumRelation *b)
 {
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_SCALAR_FIELD(oid);
+	COMPARE_SCALAR_FIELD(age);
 	COMPARE_NODE_FIELD(va_cols);
 
 	return true;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 822395625b6..e234a501e93 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -808,12 +808,13 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
  *	  create a VacuumRelation node
  */
 VacuumRelation *
-makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
+makeVacuumRelation(RangeVar *relation, Oid oid, TransactionId age, List *va_cols)
 {
 	VacuumRelation *v = makeNode(VacuumRelation);
 
 	v->relation = relation;
 	v->oid = oid;
+	v->age = age;
 	v->va_cols = va_cols;
 	return v;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b5966712ce1..8e6541ea165 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10947,7 +10947,7 @@ opt_name_list:
 vacuum_relation:
 			qualified_name opt_name_list
 				{
-					$$ = (Node *) makeVacuumRelation($1, InvalidOid, $2);
+					$$ = (Node *) makeVacuumRelation($1, InvalidOid, 0, $2);
 				}
 		;
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 681ef91b81e..e0f2eefd769 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3241,7 +3241,7 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
 
 	/* Set up one VacuumRelation target, identified by OID, for vacuum() */
 	rangevar = makeRangeVar(tab->at_nspname, tab->at_relname, -1);
-	rel = makeVacuumRelation(rangevar, tab->at_relid, NIL);
+	rel = makeVacuumRelation(rangevar, tab->at_relid, 0, NIL);
 	rel_list = list_make1(rel);
 
 	vacuum(rel_list, &tab->at_params, bstrategy, true);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index fe173101d12..e8038bcc5d1 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -104,6 +104,7 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
-extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid,
+										  TransactionId age, List *va_cols);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3e9bdc781f9..c25ee7389a3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3374,6 +3374,7 @@ typedef struct VacuumRelation
 	NodeTag		type;
 	RangeVar   *relation;		/* table name to process, or NULL */
 	Oid			oid;			/* table's OID; InvalidOid if not looked up */
+	TransactionId		age;			/* table's age or 0 if unknown*/
 	List	   *va_cols;		/* list of column names, or NIL for all */
 } VacuumRelation;
 
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 3e70e4c788e..c5b91871ca1 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -413,3 +413,10 @@ RESET ROLE;
 DROP TABLE vacowned;
 DROP TABLE vacowned_parted;
 DROP ROLE regress_vacuum;
+VACUUM(EMERGENCY, PROCESS_TOAST); -- fails
+ERROR:  option "process_toast" is incompatible with EMERGENCY
+LINE 1: VACUUM(EMERGENCY, PROCESS_TOAST);
+                          ^
+VACUUM(EMERGENCY) pg_class; -- fails
+ERROR:  a relation list is not supported with the EMERGENCY option
+VACUUM(EMERGENCY);
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 18cb7fd08ac..e41faa93590 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -318,3 +318,7 @@ RESET ROLE;
 DROP TABLE vacowned;
 DROP TABLE vacowned_parted;
 DROP ROLE regress_vacuum;
+
+VACUUM(EMERGENCY, PROCESS_TOAST); -- fails
+VACUUM(EMERGENCY) pg_class; -- fails
+VACUUM(EMERGENCY);
-- 
2.17.1

