From 9d380da6c69a11f6d85ac97084777623a81be37f Mon Sep 17 00:00:00 2001
From: Shayon Mukherjee <shayonj@gmail.com>
Date: Tue, 15 Jul 2025 07:31:39 -0400
Subject: [PATCH v1] Vacuum truncate inheritance support

---
 src/backend/commands/vacuum.c | 132 ++++++++++++++++++++++++++++++++++
 1 file changed, 132 insertions(+)

diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7..65dda2f52c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2220,18 +2220,138 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	if (params.truncate == VACOPTVALUE_UNSPECIFIED)
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
+		bool is_toast = IsToastRelation(rel);
+
+		ereport(LOG,
+				(errmsg("VACUUM TRUNCATE DEBUG: relation \"%s\" (OID %u), is_toast=%s, autovacuum_worker=%s",
+						RelationGetRelationName(rel),
+						RelationGetRelid(rel),
+						is_toast ? "true" : "false",
+						AmAutoVacuumWorkerProcess() ? "true" : "false")));
 
 		if (opts && opts->vacuum_truncate_set)
 		{
+			ereport(LOG,
+					(errmsg("VACUUM TRUNCATE DEBUG: relation \"%s\" has explicit vacuum_truncate=%s",
+							RelationGetRelationName(rel),
+							opts->vacuum_truncate ? "true" : "false")));
 			if (opts->vacuum_truncate)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
 		}
+		else if (is_toast)
+		{
+			/*
+			 * For TOAST tables without explicit settings, inherit from parent table.
+			 * This ensures that vacuum behavior is consistent between main tables
+			 * and their TOAST tables, which is important for operations like
+			 * truncation that can block queries.
+			 */
+			Relation	pgclass;
+			SysScanDesc scan;
+			ScanKeyData key;
+			HeapTuple	tuple;
+			Oid			parent_relid = InvalidOid;
+			bool		found_parent = false;
+			bool		parent_truncate_set = false;
+			bool		parent_truncate = false;
+
+			/* Find the parent table by scanning for reltoastrelid match */
+			pgclass = table_open(RelationRelationId, AccessShareLock);
+
+			ScanKeyInit(&key,
+						Anum_pg_class_reltoastrelid,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(RelationGetRelid(rel)));
+
+			scan = systable_beginscan(pgclass, InvalidOid, false,
+									  NULL, 1, &key);
+
+			while ((tuple = systable_getnext(scan)) != NULL)
+			{
+				Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+				parent_relid = classForm->oid;
+				found_parent = true;
+				break;
+			}
+
+			systable_endscan(scan);
+			table_close(pgclass, AccessShareLock);
+
+			if (found_parent)
+			{
+				Relation parent_rel;
+				StdRdOptions *parent_opts;
+
+				/* Open parent relation to get its options */
+				parent_rel = table_open(parent_relid, AccessShareLock);
+				parent_opts = (StdRdOptions *) parent_rel->rd_options;
+
+				if (parent_opts && parent_opts->vacuum_truncate_set)
+				{
+					parent_truncate_set = true;
+					parent_truncate = parent_opts->vacuum_truncate;
+				}
+
+				table_close(parent_rel, AccessShareLock);
+
+				if (parent_truncate_set)
+				{
+					ereport(LOG,
+							(errmsg("VACUUM TRUNCATE DEBUG: relation \"%s\" inheriting vacuum_truncate=%s from parent OID %u",
+									RelationGetRelationName(rel),
+									parent_truncate ? "true" : "false",
+									parent_relid)));
+					if (parent_truncate)
+						params.truncate = VACOPTVALUE_ENABLED;
+					else
+						params.truncate = VACOPTVALUE_DISABLED;
+				}
+				else
+				{
+					ereport(LOG,
+							(errmsg("VACUUM TRUNCATE DEBUG: relation \"%s\" parent has no explicit setting, using global default vacuum_truncate=%s",
+									RelationGetRelationName(rel),
+									vacuum_truncate ? "true" : "false")));
+					if (vacuum_truncate)
+						params.truncate = VACOPTVALUE_ENABLED;
+					else
+						params.truncate = VACOPTVALUE_DISABLED;
+				}
+			}
+			else
+			{
+				ereport(LOG,
+						(errmsg("VACUUM TRUNCATE DEBUG: relation \"%s\" parent not found, using global default vacuum_truncate=%s",
+								RelationGetRelationName(rel),
+								vacuum_truncate ? "true" : "false")));
+				if (vacuum_truncate)
+					params.truncate = VACOPTVALUE_ENABLED;
+				else
+					params.truncate = VACOPTVALUE_DISABLED;
+			}
+		}
 		else if (vacuum_truncate)
+		{
+			ereport(LOG,
+					(errmsg("VACUUM TRUNCATE DEBUG: relation \"%s\" using global default vacuum_truncate=true",
+							RelationGetRelationName(rel))));
 			params.truncate = VACOPTVALUE_ENABLED;
+		}
 		else
+		{
+			ereport(LOG,
+					(errmsg("VACUUM TRUNCATE DEBUG: relation \"%s\" using global default vacuum_truncate=false",
+							RelationGetRelationName(rel))));
 			params.truncate = VACOPTVALUE_DISABLED;
+		}
+
+		ereport(LOG,
+				(errmsg("VACUUM TRUNCATE DEBUG: relation \"%s\" final decision: params.truncate=%s",
+						RelationGetRelationName(rel),
+						params.truncate == VACOPTVALUE_ENABLED ? "ENABLED" :
+						params.truncate == VACOPTVALUE_DISABLED ? "DISABLED" : "OTHER")));
 	}
 
 #ifdef USE_INJECTION_POINTS
@@ -2330,6 +2450,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
 		toast_vacuum_params.toast_parent = relid;
 
+		/* Inherit the main table's final truncate decision */
+		toast_vacuum_params.truncate = params.truncate;
+
+		ereport(LOG,
+				(errmsg("VACUUM TRUNCATE DEBUG: calling vacuum_rel for TOAST table OID %u, parent \"%s\" (OID %u), inherited params.truncate=%s",
+						toast_relid,
+						RelationGetRelationName(rel),
+						relid,
+						toast_vacuum_params.truncate == VACOPTVALUE_ENABLED ? "ENABLED" :
+						toast_vacuum_params.truncate == VACOPTVALUE_DISABLED ? "DISABLED" :
+						toast_vacuum_params.truncate == VACOPTVALUE_UNSPECIFIED ? "UNSPECIFIED" : "OTHER")));
+
 		vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy);
 	}
 
-- 
2.39.5 (Apple Git-154)

