From f362b542c11975d288ac57bc24d894d3e7950ab6 Mon Sep 17 00:00:00 2001
From: Sami Imseih <samimseih@gmail.com>
Date: Mon, 10 Aug 2026 21:51:31 +0000
Subject: [PATCH v2 2/2] test autovacuum toast reloptions

---
 src/backend/commands/vacuum.c                 | 26 ++++--
 src/test/modules/test_misc/meson.build        |  1 +
 .../t/015_autovacuum_toast_reloptions.pl      | 82 +++++++++++++++++++
 3 files changed, 103 insertions(+), 6 deletions(-)
 create mode 100644 src/test/modules/test_misc/t/015_autovacuum_toast_reloptions.pl

diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 31f9824899c..f535488631c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2228,12 +2228,22 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	}
 
 #ifdef USE_INJECTION_POINTS
+
+	/*
+	 * An autovacuum worker processes many relations, so it passes the
+	 * relation name for a test to match with a condition string.  A manual
+	 * VACUUM is tested with a local injection point, so it does not need to
+	 * deal with other relations.
+	 */
 	if (params.index_cleanup == VACOPTVALUE_AUTO)
-		INJECTION_POINT("vacuum-index-cleanup-auto", NULL);
+		INJECTION_POINT("vacuum-index-cleanup-auto",
+						AmAutoVacuumWorkerProcess() ? RelationGetRelationName(rel) : NULL);
 	else if (params.index_cleanup == VACOPTVALUE_DISABLED)
-		INJECTION_POINT("vacuum-index-cleanup-disabled", NULL);
+		INJECTION_POINT("vacuum-index-cleanup-disabled",
+						AmAutoVacuumWorkerProcess() ? RelationGetRelationName(rel) : NULL);
 	else if (params.index_cleanup == VACOPTVALUE_ENABLED)
-		INJECTION_POINT("vacuum-index-cleanup-enabled", NULL);
+		INJECTION_POINT("vacuum-index-cleanup-enabled",
+						AmAutoVacuumWorkerProcess() ? RelationGetRelationName(rel) : NULL);
 #endif
 
 	/*
@@ -2267,12 +2277,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	}
 
 #ifdef USE_INJECTION_POINTS
+	/* See the comment for the index_cleanup injection points above. */
 	if (params.truncate == VACOPTVALUE_AUTO)
-		INJECTION_POINT("vacuum-truncate-auto", NULL);
+		INJECTION_POINT("vacuum-truncate-auto",
+						AmAutoVacuumWorkerProcess() ? RelationGetRelationName(rel) : NULL);
 	else if (params.truncate == VACOPTVALUE_DISABLED)
-		INJECTION_POINT("vacuum-truncate-disabled", NULL);
+		INJECTION_POINT("vacuum-truncate-disabled",
+						AmAutoVacuumWorkerProcess() ? RelationGetRelationName(rel) : NULL);
 	else if (params.truncate == VACOPTVALUE_ENABLED)
-		INJECTION_POINT("vacuum-truncate-enabled", NULL);
+		INJECTION_POINT("vacuum-truncate-enabled",
+						AmAutoVacuumWorkerProcess() ? RelationGetRelationName(rel) : NULL);
 #endif
 
 	/*
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index ee290698b31..f03c8d09c76 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -23,6 +23,7 @@ tests += {
       't/012_ddlutils.pl',
       't/013_temp_obj_multisession.pl',
       't/014_log_statement_max_length.pl',
+      't/015_autovacuum_toast_reloptions.pl',
     ],
     # The injection points are cluster-wide, so disable installcheck
     'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/015_autovacuum_toast_reloptions.pl b/src/test/modules/test_misc/t/015_autovacuum_toast_reloptions.pl
new file mode 100644
index 00000000000..2fa18466045
--- /dev/null
+++ b/src/test/modules/test_misc/t/015_autovacuum_toast_reloptions.pl
@@ -0,0 +1,82 @@
+# Copyright (c) 2024-2026, PostgreSQL Global Development Group
+
+# A TOAST relation inherits vacuum reloptions from its main table when it has
+# no toast.* value of its own.  Condition strings key each wait to the TOAST
+# relation, so its resolution is observed on its own.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use Test::More;
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+
+# Run autovacuum quickly and eagerly.
+$node->append_conf(
+	'postgresql.conf', qq(
+autovacuum_naptime = 1
+autovacuum_vacuum_threshold = 1
+autovacuum_vacuum_scale_factor = 0
+));
+$node->start;
+
+if (!$node->check_extension('injection_points'))
+{
+	plan skip_all => 'Extension injection_points not installed';
+}
+
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+# Set non-default options on the main table, none on the TOAST relation, so it
+# inherits them.
+$node->safe_psql(
+	'postgres', qq(
+    CREATE TABLE tab (i int, j text) WITH (
+        autovacuum_enabled=false,
+        vacuum_index_cleanup=false,
+        vacuum_truncate=false);
+    ALTER TABLE tab ALTER COLUMN j SET STORAGE EXTERNAL;
+    INSERT INTO tab SELECT g, repeat('x', 3000) FROM generate_series(1, 1000) g;
+    DELETE FROM tab;
+));
+
+my $toast_rel = $node->safe_psql(
+	'postgres', qq(
+    SELECT relname FROM pg_class
+    WHERE oid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'tab'::regclass);
+));
+
+$node->safe_psql(
+	'postgres', qq(
+    SELECT injection_points_attach('vacuum-index-cleanup-disabled', 'wait', '$toast_rel');
+    SELECT injection_points_attach('vacuum-truncate-disabled', 'wait', '$toast_rel');
+));
+
+# Let autovacuum take the table.
+$node->safe_psql('postgres', 'ALTER TABLE tab SET (autovacuum_enabled=true);');
+$node->reload();
+
+$node->wait_for_event('autovacuum worker', 'vacuum-index-cleanup-disabled');
+pass('TOAST relation inherited vacuum_index_cleanup=false');
+
+$node->safe_psql('postgres',
+	"SELECT injection_points_wakeup('vacuum-index-cleanup-disabled');");
+
+$node->wait_for_event('autovacuum worker', 'vacuum-truncate-disabled');
+pass('TOAST relation inherited vacuum_truncate=false');
+
+# Release and clean up.
+$node->safe_psql(
+	'postgres', qq(
+    SELECT injection_points_wakeup('vacuum-truncate-disabled');
+    SELECT injection_points_detach('vacuum-index-cleanup-disabled');
+    SELECT injection_points_detach('vacuum-truncate-disabled');
+));
+
+done_testing();
-- 
2.47.3

