On Mon, Aug 10, 2026 at 11:30:17AM -0500, Nathan Bossart wrote:
> On Sun, Aug 09, 2026 at 09:37:49AM -0400, Greg Burd wrote:
>> In merge_autovac_opts() the four offset arrays keyed by "which sentinel
>> means unset", is that duplicating knowledge that already lives in the
>> relopt tables in reloptions.c?
>> 
>> [...] 
>> 
>> Add an AutoVacOpts field, or change a field's default sentinel, and
>> forget to update the matching array here, and the merge silently keeps
>> the TOAST table's default instead of inheriting, nothing fails to compile
>> and no test goes red.  Can this be driven off the relopt metadata
>> (relopt_parse_elt already knows each option's type and default) instead
>> of the hand-maintained offset arrays?
> 
> I'm looking into this.  Since this is almost certainly a master-only change
> at this point, it seems reasonable to spend some more time on making this
> stuff less fragile.

Here is a new patch set with an attempt at the above.  The new 0005
contains the log_autovacuum_min_duration default change along with an
assertion that all TOAST storage parameters have unsettable defaults.  0006
is just some prerequisite refactoring for 0007.  And 0007 is the fix with
the merge done based on the main reloption list instead of new arrays.

>> Nothing exercises the autovacuum decision path, autovacuum_enabled
>> inheritance, or any of the numeric AutoVacOpts that merge_autovac_opts()
>> actually resolves which is precisely the code I'm worried about above.
>> FWIW the pg_stat_get_autovacuum_scores() SRF that 0005 extends looks like
>> it could drive a deterministic test of the autovac path (compute the
>> decision without spawning a worker), which sidesteps the flakiness worry
>> raised upthread.
> 
> Will add some more coverage.

Done.

-- 
nathan
>From d26b57020009e22f0e969f0357def265066e191c Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Mon, 8 Jun 2026 14:35:34 -0500
Subject: [PATCH v8 1/7] Remove extract_autovac_opts().

extract_autovac_opts() returned a palloc'd copy of only the
AutoVacOpts portion of a relation's reloptions.  Upcoming work
needs the rest of the StdRdOptions as well, so the callers must
keep the whole struct around.

Remove the helper and have the callers obtain reloptions from
extractRelOptions() directly.  av_relation now caches a
StdRdOptions instead of an AutoVacOpts, and
relation_needs_vacanalyze() takes a StdRdOptions and extracts the
autovacuum portion itself.  This is preparatory refactoring with no
change in behavior.
---
 src/backend/postmaster/autovacuum.c | 122 ++++++++++------------------
 1 file changed, 44 insertions(+), 78 deletions(-)

diff --git a/src/backend/postmaster/autovacuum.c 
b/src/backend/postmaster/autovacuum.c
index ee202a4b47e..db1f5d0e575 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -202,8 +202,7 @@ typedef struct av_relation
        Oid                     ar_toastrelid;  /* hash key - must be first */
        Oid                     ar_relid;
        bool            ar_hasrelopts;
-       AutoVacOpts ar_reloptions;      /* copy of AutoVacOpts from the main 
table's
-                                                                * reloptions, 
or NULL if none */
+       StdRdOptions ar_reloptions; /* copy of main table's reloptions */
 } av_relation;
 
 /* struct to keep track of tables to vacuum and/or analyze, after rechecking */
@@ -379,7 +378,7 @@ static void FreeWorkerInfo(int code, Datum arg);
 static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map,
                                                                                
        TupleDesc pg_class_desc,
                                                                                
        int effective_multixact_freeze_max_age);
-static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
+static void relation_needs_vacanalyze(Oid relid, StdRdOptions *relopts,
                                                                          
Form_pg_class classForm,
                                                                          int 
effective_multixact_freeze_max_age,
                                                                          int 
elevel,
@@ -388,8 +387,6 @@ static void relation_needs_vacanalyze(Oid relid, 
AutoVacOpts *relopts,
 
 static void autovacuum_do_vac_analyze(autovac_table *tab,
                                                                          
BufferAccessStrategy bstrategy);
-static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
-                                                                               
 TupleDesc pg_class_desc);
 static void perform_work_item(AutoVacuumWorkItem *workitem);
 static void autovac_report_activity(autovac_table *tab);
 static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
@@ -2037,7 +2034,7 @@ do_autovacuum(void)
        while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL)
        {
                Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
-               AutoVacOpts *relopts;
+               StdRdOptions *relopts;
                Oid                     relid;
                bool            dovacuum;
                bool            doanalyze;
@@ -2076,7 +2073,7 @@ do_autovacuum(void)
                }
 
                /* Fetch reloptions and the pgstat entry for this table */
-               relopts = extract_autovac_opts(tuple, pg_class_desc);
+               relopts = (StdRdOptions *) extractRelOptions(tuple, 
pg_class_desc, NULL);
 
                /* Check if it needs vacuum or analyze */
                relation_needs_vacanalyze(relid, relopts, classForm,
@@ -2118,7 +2115,7 @@ do_autovacuum(void)
                                {
                                        hentry->ar_hasrelopts = true;
                                        memcpy(&hentry->ar_reloptions, relopts,
-                                                  sizeof(AutoVacOpts));
+                                                  sizeof(StdRdOptions));
                                }
                        }
                }
@@ -2141,7 +2138,7 @@ do_autovacuum(void)
        {
                Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
                Oid                     relid;
-               AutoVacOpts *relopts;
+               StdRdOptions *relopts;
                bool            free_relopts = false;
                bool            dovacuum;
                bool            doanalyze;
@@ -2160,7 +2157,7 @@ do_autovacuum(void)
                 * fetch reloptions -- if this toast table does not have them, 
try the
                 * main rel
                 */
-               relopts = extract_autovac_opts(tuple, pg_class_desc);
+               relopts = (StdRdOptions *) extractRelOptions(tuple, 
pg_class_desc, NULL);
                if (relopts)
                        free_relopts = true;
                else
@@ -2776,39 +2773,6 @@ deleted2:
                pfree(cur_relname);
 }
 
-/*
- * extract_autovac_opts
- *
- * Given a relation's pg_class tuple, return a palloc'd copy of the
- * AutoVacOpts portion of reloptions, if set; otherwise, return NULL.
- *
- * Note: callers do not have a relation lock on the table at this point,
- * so the table could have been dropped, and its catalog rows gone, after
- * we acquired the pg_class row.  If pg_class had a TOAST table, this would
- * be a risk; fortunately, it doesn't.
- */
-static AutoVacOpts *
-extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
-{
-       bytea      *relopts;
-       AutoVacOpts *av;
-
-       Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION ||
-                  ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW 
||
-                  ((Form_pg_class) GETSTRUCT(tup))->relkind == 
RELKIND_TOASTVALUE);
-
-       relopts = extractRelOptions(tup, pg_class_desc, NULL);
-       if (relopts == NULL)
-               return NULL;
-
-       av = palloc_object(AutoVacOpts);
-       memcpy(av, &(((StdRdOptions *) relopts)->autovacuum), 
sizeof(AutoVacOpts));
-       pfree(relopts);
-
-       return av;
-}
-
-
 /*
  * table_recheck_autovac
  *
@@ -2828,8 +2792,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
        bool            doanalyze;
        autovac_table *tab = NULL;
        bool            wraparound;
-       AutoVacOpts *avopts;
-       bool            free_avopts = false;
+       StdRdOptions *relopts;
+       bool            free_relopts = false;
        AutoVacuumScores scores;
 
        /* fetch the relation's relcache entry */
@@ -2842,9 +2806,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
         * Get the applicable reloptions.  If it is a TOAST table, try to get 
the
         * main table reloptions if the toast table itself doesn't have.
         */
-       avopts = extract_autovac_opts(classTup, pg_class_desc);
-       if (avopts)
-               free_avopts = true;
+       relopts = (StdRdOptions *) extractRelOptions(classTup, pg_class_desc, 
NULL);
+       if (relopts)
+               free_relopts = true;
        else if (classForm->relkind == RELKIND_TOASTVALUE &&
                         table_toast_map != NULL)
        {
@@ -2853,10 +2817,10 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 
                hentry = hash_search(table_toast_map, &relid, HASH_FIND, 
&found);
                if (found && hentry->ar_hasrelopts)
-                       avopts = &hentry->ar_reloptions;
+                       relopts = &hentry->ar_reloptions;
        }
 
-       relation_needs_vacanalyze(relid, avopts, classForm,
+       relation_needs_vacanalyze(relid, relopts, classForm,
                                                          
effective_multixact_freeze_max_age,
                                                          DEBUG3,
                                                          &dovacuum, 
&doanalyze, &wraparound,
@@ -2871,6 +2835,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
                int                     multixact_freeze_table_age;
                int                     log_vacuum_min_duration;
                int                     log_analyze_min_duration;
+               AutoVacOpts *avopts = (relopts ? &relopts->autovacuum : NULL);
 
                /*
                 * Calculate the vacuum cost parameters and the freeze ages.  
If there
@@ -2982,8 +2947,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
                                                 avopts->vacuum_cost_delay >= 
0));
        }
 
-       if (free_avopts)
-               pfree(avopts);
+       if (free_relopts)
+               pfree(relopts);
        heap_freetuple(classTup);
        return tab;
 }
@@ -2995,7 +2960,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
  * "dovacuum" and "doanalyze", respectively.  Also return whether the vacuum is
  * being forced because of Xid or multixact wraparound.
  *
- * relopts is a pointer to the AutoVacOpts options (either for itself in the
+ * relopts is a pointer to the StdRdOptions options (either for itself in the
  * case of a plain table, or for either itself or its parent table in the case
  * of a TOAST table), NULL if none.
  *
@@ -3072,7 +3037,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
  */
 static void
 relation_needs_vacanalyze(Oid relid,
-                                                 AutoVacOpts *relopts,
+                                                 StdRdOptions *relopts,
                                                  Form_pg_class classForm,
                                                  int 
effective_multixact_freeze_max_age,
                                                  int elevel,
@@ -3086,6 +3051,7 @@ relation_needs_vacanalyze(Oid relid,
        bool            force_vacuum;
        bool            av_enabled;
        bool            may_free = false;
+       AutoVacOpts *avopts = (relopts ? &relopts->autovacuum : NULL);
 
        /* constants from reloptions or GUC variables */
        int                     vac_base_thresh,
@@ -3137,45 +3103,45 @@ relation_needs_vacanalyze(Oid relid,
         */
 
        /* -1 in autovac setting means use plain vacuum_scale_factor */
-       vac_scale_factor = (relopts && relopts->vacuum_scale_factor >= 0)
-               ? relopts->vacuum_scale_factor
+       vac_scale_factor = (avopts && avopts->vacuum_scale_factor >= 0)
+               ? avopts->vacuum_scale_factor
                : autovacuum_vac_scale;
 
-       vac_base_thresh = (relopts && relopts->vacuum_threshold >= 0)
-               ? relopts->vacuum_threshold
+       vac_base_thresh = (avopts && avopts->vacuum_threshold >= 0)
+               ? avopts->vacuum_threshold
                : autovacuum_vac_thresh;
 
        /* -1 is used to disable max threshold */
-       vac_max_thresh = (relopts && relopts->vacuum_max_threshold >= -1)
-               ? relopts->vacuum_max_threshold
+       vac_max_thresh = (avopts && avopts->vacuum_max_threshold >= -1)
+               ? avopts->vacuum_max_threshold
                : autovacuum_vac_max_thresh;
 
-       vac_ins_scale_factor = (relopts && relopts->vacuum_ins_scale_factor >= 
0)
-               ? relopts->vacuum_ins_scale_factor
+       vac_ins_scale_factor = (avopts && avopts->vacuum_ins_scale_factor >= 0)
+               ? avopts->vacuum_ins_scale_factor
                : autovacuum_vac_ins_scale;
 
        /* -1 is used to disable insert vacuums */
-       vac_ins_base_thresh = (relopts && relopts->vacuum_ins_threshold >= -1)
-               ? relopts->vacuum_ins_threshold
+       vac_ins_base_thresh = (avopts && avopts->vacuum_ins_threshold >= -1)
+               ? avopts->vacuum_ins_threshold
                : autovacuum_vac_ins_thresh;
 
-       anl_scale_factor = (relopts && relopts->analyze_scale_factor >= 0)
-               ? relopts->analyze_scale_factor
+       anl_scale_factor = (avopts && avopts->analyze_scale_factor >= 0)
+               ? avopts->analyze_scale_factor
                : autovacuum_anl_scale;
 
-       anl_base_thresh = (relopts && relopts->analyze_threshold >= 0)
-               ? relopts->analyze_threshold
+       anl_base_thresh = (avopts && avopts->analyze_threshold >= 0)
+               ? avopts->analyze_threshold
                : autovacuum_anl_thresh;
 
-       freeze_max_age = (relopts && relopts->freeze_max_age >= 0)
-               ? Min(relopts->freeze_max_age, autovacuum_freeze_max_age)
+       freeze_max_age = (avopts && avopts->freeze_max_age >= 0)
+               ? Min(avopts->freeze_max_age, autovacuum_freeze_max_age)
                : autovacuum_freeze_max_age;
 
-       multixact_freeze_max_age = (relopts && 
relopts->multixact_freeze_max_age >= 0)
-               ? Min(relopts->multixact_freeze_max_age, 
effective_multixact_freeze_max_age)
+       multixact_freeze_max_age = (avopts && avopts->multixact_freeze_max_age 
>= 0)
+               ? Min(avopts->multixact_freeze_max_age, 
effective_multixact_freeze_max_age)
                : effective_multixact_freeze_max_age;
 
-       av_enabled = (relopts ? relopts->enabled : true);
+       av_enabled = (avopts ? avopts->enabled : true);
        av_enabled &= AutoVacuumingActive();
 
        relfrozenxid = classForm->relfrozenxid;
@@ -3668,7 +3634,7 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
        while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
        {
                Form_pg_class form = (Form_pg_class) GETSTRUCT(tup);
-               AutoVacOpts *avopts;
+               StdRdOptions *relopts;
                bool            dovacuum;
                bool            doanalyze;
                bool            wraparound;
@@ -3684,14 +3650,14 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
                if (form->relpersistence == RELPERSISTENCE_TEMP)
                        continue;
 
-               avopts = extract_autovac_opts(tup, RelationGetDescr(rel));
-               relation_needs_vacanalyze(form->oid, avopts, form,
+               relopts = (StdRdOptions *) extractRelOptions(tup, 
RelationGetDescr(rel), NULL);
+               relation_needs_vacanalyze(form->oid, relopts, form,
                                                                  
effective_multixact_freeze_max_age,
                                                                  LOG_NEVER,
                                                                  &dovacuum, 
&doanalyze, &wraparound,
                                                                  &scores);
-               if (avopts)
-                       pfree(avopts);
+               if (relopts)
+                       pfree(relopts);
 
                vals[0] = ObjectIdGetDatum(form->oid);
                vals[1] = Float8GetDatum(scores.max);
-- 
2.50.1 (Apple Git-155)

>From 4a3e2a8b50cd2c67b896f4c0853fae201e3f146f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Mon, 8 Jun 2026 14:50:36 -0500
Subject: [PATCH v8 2/7] Make autovacuum_enabled a ternary reloption.

This commit reimplements autovacuum_enabled as a ternary, using the
support added in commit 4d6a66f675 and following the example of
vacuum_truncate.  This changes only the internal representation: an
unset value still behaves as enabled, and the option accepts the
same input as before.

This is preparatory work for a follow-up commit that will make use
of the new "unset" state.
---
 src/backend/access/common/reloptions.c | 19 +++++++++----------
 src/backend/catalog/index.c            |  3 ++-
 src/backend/postmaster/autovacuum.c    |  2 +-
 src/include/utils/rel.h                |  2 +-
 4 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/backend/access/common/reloptions.c 
b/src/backend/access/common/reloptions.c
index 3e832c3797e..79834126f2f 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -107,15 +107,6 @@ static relopt_bool boolRelOpts[] =
                },
                false
        },
-       {
-               {
-                       "autovacuum_enabled",
-                       "Enables autovacuum in this relation",
-                       RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-                       ShareUpdateExclusiveLock
-               },
-               true
-       },
        {
                {
                        "user_catalog_table",
@@ -168,6 +159,14 @@ static relopt_bool boolRelOpts[] =
 
 static relopt_ternary ternaryRelOpts[] =
 {
+       {
+               {
+                       "autovacuum_enabled",
+                       "Enables autovacuum in this relation",
+                       RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+                       ShareUpdateExclusiveLock
+               }
+       },
        {
                {
                        "vacuum_truncate",
@@ -1976,7 +1975,7 @@ default_reloptions(Datum reloptions, bool validate, 
relopt_kind kind)
 {
        static const relopt_parse_elt tab[] = {
                {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, 
fillfactor)},
-               {"autovacuum_enabled", RELOPT_TYPE_BOOL,
+               {"autovacuum_enabled", RELOPT_TYPE_TERNARY,
                offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
enabled)},
                {"autovacuum_parallel_workers", RELOPT_TYPE_INT,
                offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
autovacuum_parallel_workers)},
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 4c5da7e5db0..ec21b83b6b8 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2900,7 +2900,8 @@ index_update_stats(Relation rel,
                {
                        StdRdOptions *options = (StdRdOptions *) 
rel->rd_options;
 
-                       if (options != NULL && !options->autovacuum.enabled)
+                       if (options != NULL &&
+                               options->autovacuum.enabled == PG_TERNARY_FALSE)
                                update_stats = false;
                }
                else
diff --git a/src/backend/postmaster/autovacuum.c 
b/src/backend/postmaster/autovacuum.c
index db1f5d0e575..c8a1b66ca2e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -3141,7 +3141,7 @@ relation_needs_vacanalyze(Oid relid,
                ? Min(avopts->multixact_freeze_max_age, 
effective_multixact_freeze_max_age)
                : effective_multixact_freeze_max_age;
 
-       av_enabled = (avopts ? avopts->enabled : true);
+       av_enabled = (avopts ? avopts->enabled != PG_TERNARY_FALSE : true);
        av_enabled &= AutoVacuumingActive();
 
        relfrozenxid = classForm->relfrozenxid;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 89c159b133f..2ee98e9c6cc 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -310,7 +310,7 @@ typedef struct ForeignKeyCacheInfo
  /* autovacuum-related reloptions. */
 typedef struct AutoVacOpts
 {
-       bool            enabled;
+       pg_ternary      enabled;
 
        int                     autovacuum_parallel_workers;
        int                     vacuum_threshold;
-- 
2.50.1 (Apple Git-155)

>From c602fb70ed014073312f0b7b601028ca24f9b4c5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Mon, 8 Jun 2026 15:27:58 -0500
Subject: [PATCH v8 3/7] Add an "unset" value for vacuum_index_cleanup.

This commit adds a new value to StdRdOptIndexCleanup to distinguish
whether it is explicitly set, similar to ViewOptCheckOption's
VIEW_OPTION_CHECK_OPTION_NOT_SET.  This changes only the internal
representation; an unset value still defaults to AUTO, and the
option accepts the same input as before.

This is preparatory work for a follow-up commit that will make use
of the new "unset" state.
---
 src/backend/access/common/reloptions.c |  3 ++-
 src/backend/commands/vacuum.c          | 23 ++++++++++++++---------
 src/include/utils/rel.h                |  1 +
 3 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/src/backend/access/common/reloptions.c 
b/src/backend/access/common/reloptions.c
index 79834126f2f..58eb72e2339 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -517,6 +517,7 @@ static relopt_real realRelOpts[] =
 /* values from StdRdOptIndexCleanup */
 static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
 {
+       /* no value for NOT_SET */
        {"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
        {"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
        {"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
@@ -557,7 +558,7 @@ static relopt_enum enumRelOpts[] =
                        ShareUpdateExclusiveLock
                },
                StdRdOptIndexCleanupValues,
-               STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
+               STDRD_OPTION_VACUUM_INDEX_CLEANUP_NOT_SET,
                gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
        },
        {
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 31f9824899c..52116c02b59 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2210,20 +2210,25 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams 
params,
                StdRdOptIndexCleanup vacuum_index_cleanup;
 
                if (rel->rd_options == NULL)
-                       vacuum_index_cleanup = 
STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+                       vacuum_index_cleanup = 
STDRD_OPTION_VACUUM_INDEX_CLEANUP_NOT_SET;
                else
                        vacuum_index_cleanup =
                                ((StdRdOptions *) 
rel->rd_options)->vacuum_index_cleanup;
 
-               if (vacuum_index_cleanup == 
STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
-                       params.index_cleanup = VACOPTVALUE_AUTO;
-               else if (vacuum_index_cleanup == 
STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON)
-                       params.index_cleanup = VACOPTVALUE_ENABLED;
-               else
+               switch (vacuum_index_cleanup)
                {
-                       Assert(vacuum_index_cleanup ==
-                                  STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF);
-                       params.index_cleanup = VACOPTVALUE_DISABLED;
+                       case STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON:
+                               params.index_cleanup = VACOPTVALUE_ENABLED;
+                               break;
+                       case STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF:
+                               params.index_cleanup = VACOPTVALUE_DISABLED;
+                               break;
+                       case STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO:
+                               params.index_cleanup = VACOPTVALUE_AUTO;
+                               break;
+                       case STDRD_OPTION_VACUUM_INDEX_CLEANUP_NOT_SET:
+                               params.index_cleanup = VACOPTVALUE_AUTO;
+                               break;
                }
        }
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 2ee98e9c6cc..01b6e1a872c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -338,6 +338,7 @@ typedef enum StdRdOptIndexCleanup
        STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
        STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
        STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
+       STDRD_OPTION_VACUUM_INDEX_CLEANUP_NOT_SET,
 } StdRdOptIndexCleanup;
 
 typedef struct StdRdOptions
-- 
2.50.1 (Apple Git-155)

>From 04510ea61ad52f49f8486a64d4fc95c4bd35d0ab Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Fri, 7 Aug 2026 10:55:34 -0500
Subject: [PATCH v8 4/7] Simplify autovacuum's TOAST-to-main-relation
 reloptions map.

do_autovacuum() adds an entry to this map for every relation that
has a TOAST table, and uses a flag to mark the entries that have no
reloptions to pass down.  The unconditional entry made sense back
when the payload was the main relation's OID: commit 7d4c9a5793
added the map so that the TOAST pass could find the parent, whose
pg_autovacuum row supplied the settings for a TOAST table that had
none of its own.  Commit 834a6da4f7 replaced that lookup by copying
the parent's reloptions into the entry, which left the OID unread
and called for the flag, since a by-value AutoVacOpts cannot say
"not set".

An entry is now worth creating only when there is something to
inherit, so skip the relations that have no reloptions and let a
successful lookup speak for itself.  Two relations cannot share a
TOAST table, and each pass sees a single catalog snapshot, so an
insertion can never find an existing entry; assert that rather than
quietly ignoring it.

While at it, remove two more leftovers of that same conversion:
ar_relid, unread ever since, and a NULL test on table_toast_map in
table_recheck_autovac(), which used to carry the relkind test for
get_pg_autovacuum_tuple_relid() and has had no NULL to catch since
that function went away.
---
 src/backend/postmaster/autovacuum.c | 26 +++++++-------------------
 1 file changed, 7 insertions(+), 19 deletions(-)

diff --git a/src/backend/postmaster/autovacuum.c 
b/src/backend/postmaster/autovacuum.c
index c8a1b66ca2e..32efe967890 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -200,8 +200,6 @@ typedef struct avw_dbase
 typedef struct av_relation
 {
        Oid                     ar_toastrelid;  /* hash key - must be first */
-       Oid                     ar_relid;
-       bool            ar_hasrelopts;
        StdRdOptions ar_reloptions; /* copy of main table's reloptions */
 } av_relation;
 
@@ -2097,7 +2095,7 @@ do_autovacuum(void)
                 * this whether or not the table is going to be vacuumed, 
because we
                 * don't automatically vacuum toast tables along the parent 
table.
                 */
-               if (OidIsValid(classForm->reltoastrelid))
+               if (OidIsValid(classForm->reltoastrelid) && relopts)
                {
                        av_relation *hentry;
                        bool            found;
@@ -2105,19 +2103,10 @@ do_autovacuum(void)
                        hentry = hash_search(table_toast_map,
                                                                 
&classForm->reltoastrelid,
                                                                 HASH_ENTER, 
&found);
+                       Assert(!found);         /* rels cannot share a TOAST 
table */
 
-                       if (!found)
-                       {
-                               /* hash_search already filled in the key */
-                               hentry->ar_relid = relid;
-                               hentry->ar_hasrelopts = false;
-                               if (relopts != NULL)
-                               {
-                                       hentry->ar_hasrelopts = true;
-                                       memcpy(&hentry->ar_reloptions, relopts,
-                                                  sizeof(StdRdOptions));
-                               }
-                       }
+                       /* hash_search already filled in the key */
+                       memcpy(&hentry->ar_reloptions, relopts, 
sizeof(StdRdOptions));
                }
 
                /* Release stuff to avoid per-relation leakage */
@@ -2166,7 +2155,7 @@ do_autovacuum(void)
                        bool            found;
 
                        hentry = hash_search(table_toast_map, &relid, 
HASH_FIND, &found);
-                       if (found && hentry->ar_hasrelopts)
+                       if (found)
                                relopts = &hentry->ar_reloptions;
                }
 
@@ -2809,14 +2798,13 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
        relopts = (StdRdOptions *) extractRelOptions(classTup, pg_class_desc, 
NULL);
        if (relopts)
                free_relopts = true;
-       else if (classForm->relkind == RELKIND_TOASTVALUE &&
-                        table_toast_map != NULL)
+       else if (classForm->relkind == RELKIND_TOASTVALUE)
        {
                av_relation *hentry;
                bool            found;
 
                hentry = hash_search(table_toast_map, &relid, HASH_FIND, 
&found);
-               if (found && hentry->ar_hasrelopts)
+               if (found)
                        relopts = &hentry->ar_reloptions;
        }
 
-- 
2.50.1 (Apple Git-155)

>From c0327f11b5b4e1ace51e7d411aef87a176df72c5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Mon, 10 Aug 2026 15:55:44 -0500
Subject: [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults.

This is preparatory work for a follow-up commit that will fill in a
TOAST table's unset storage parameters from its main table's.  For
that, it must be possible to tell an option nobody set from one the
user set to the value that option happens to default to.  The
parsed form of a relation's options has nowhere to record which
ones were specified, so an unset option is simply one still holding
its declared default.

Require, then, that any option a TOAST table accepts default to a
value the user cannot set.  Ternaries already comply, having no
default at all, as does vacuum_index_cleanup, whose "not set"
member has no spelling.  Among the numeric ones only
log_autovacuum_min_duration was in violation, defaulting to -1 with
a minimum of -1, so give it -2 instead, matching
autovacuum_vacuum_max_threshold and
autovacuum_vacuum_insert_threshold.  Users won't notice; -1 already
behaved exactly as leaving the option unset does, and it still
does.

An assertion in initialize_reloptions() enforces both of the rules
the follow-up commit will rely on: that the default is unsettable,
and that anything settable on a TOAST table is settable on a heap,
since the inherited value is read from a main table's options at
the same offset.  Note that this rules out bool and string options,
neither of which can express "unset".
---
 src/backend/access/common/reloptions.c | 78 +++++++++++++++++++++++++-
 src/backend/postmaster/autovacuum.c    |  2 +-
 2 files changed, 78 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/common/reloptions.c 
b/src/backend/access/common/reloptions.c
index 58eb72e2339..02bc8213def 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -348,7 +348,7 @@ static relopt_int intRelOpts[] =
                        RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
                        ShareUpdateExclusiveLock
                },
-               -1, -1, INT_MAX
+               -2, -1, INT_MAX
        },
        {
                {
@@ -613,6 +613,78 @@ static void parse_one_reloption(relopt_value *option, char 
*text_str,
        ((option).isset ? strlen((option).string_val) : \
         ((relopt_string *) (option).gen)->default_len)
 
+#ifdef USE_ASSERT_CHECKING
+/*
+ * Verify that every option a TOAST table accepts defaults to a value the user
+ * cannot set.  Nothing records which options were specified, so an option
+ * still holding its default is the only way to recognize one that was never
+ * set, and that is how a TOAST table tells which values it should take from
+ * its main table.
+ */
+static void
+assert_toast_defaults_unsettable(void)
+{
+       for (int i = 0; relOpts[i]; i++)
+       {
+               relopt_gen *gen = relOpts[i];
+
+               if ((gen->kinds & RELOPT_KIND_TOAST) == 0)
+                       continue;
+
+               /*
+                * A TOAST table's value is filled in from its main table's at 
the
+                * same offset in the same struct, so the option must be 
settable on a
+                * heap too.
+                */
+               Assert((gen->kinds & RELOPT_KIND_HEAP) != 0);
+
+               switch (gen->type)
+               {
+                       case RELOPT_TYPE_TERNARY:
+
+                               /*
+                                * Ternaries carry no default, and 
parse_one_reloption() can
+                                * only produce true or false, so 
PG_TERNARY_UNSET is already
+                                * beyond a user's reach.
+                                */
+                               break;
+
+                       case RELOPT_TYPE_INT:
+                               {
+                                       relopt_int *optint = (relopt_int *) gen;
+
+                                       Assert(optint->default_val < 
optint->min ||
+                                                  optint->default_val > 
optint->max);
+                                       break;
+                               }
+
+                       case RELOPT_TYPE_REAL:
+                               {
+                                       relopt_real *optreal = (relopt_real *) 
gen;
+
+                                       Assert(optreal->default_val < 
optreal->min ||
+                                                  optreal->default_val > 
optreal->max);
+                                       break;
+                               }
+
+                       case RELOPT_TYPE_ENUM:
+                               {
+                                       relopt_enum *optenum = (relopt_enum *) 
gen;
+
+                                       for (relopt_enum_elt_def *elt = 
optenum->members;
+                                                elt->string_val; elt++)
+                                               Assert(elt->symbol_val != 
optenum->default_val);
+                                       break;
+                               }
+
+                       default:
+                               /* Neither bools nor strings can express 
"unset". */
+                               Assert(false);
+               }
+       }
+}
+#endif                                                 /* USE_ASSERT_CHECKING 
*/
+
 /*
  * initialize_reloptions
  *             initialization routine, must be called before parsing
@@ -730,6 +802,10 @@ initialize_reloptions(void)
 
        /* flag the work is complete */
        need_initialization = false;
+
+#ifdef USE_ASSERT_CHECKING
+       assert_toast_defaults_unsettable();
+#endif
 }
 
 /*
diff --git a/src/backend/postmaster/autovacuum.c 
b/src/backend/postmaster/autovacuum.c
index 32efe967890..a99f7108636 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2832,7 +2832,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
                 * defaults, autovacuum's own first and plain vacuum second.
                 */
 
-               /* -1 in autovac setting means use log_autovacuum_min_duration 
*/
+               /* a negative autovac setting means use 
log_autovacuum_min_duration */
                log_vacuum_min_duration = (avopts && 
avopts->log_vacuum_min_duration >= 0)
                        ? avopts->log_vacuum_min_duration
                        : Log_autovacuum_min_duration;
-- 
2.50.1 (Apple Git-155)

>From 86346bdce4bd1182520348c40151a695da8b53e2 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Mon, 10 Aug 2026 15:56:02 -0500
Subject: [PATCH v8 6/7] Move the StdRdOptions parse table to file scope.

This is preparatory work for a follow-up commit that will walk the
table from another function.  Nothing changes but the indentation
of its entries.
---
 src/backend/access/common/reloptions.c | 115 +++++++++++++------------
 1 file changed, 60 insertions(+), 55 deletions(-)

diff --git a/src/backend/access/common/reloptions.c 
b/src/backend/access/common/reloptions.c
index 02bc8213def..4548eb02676 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -2044,69 +2044,74 @@ fillRelOptions(void *rdopts, Size basesize,
 }
 
 
+/*
+ * Parse table for StdRdOptions, which is shared by the RELOPT_KIND_HEAP and
+ * RELOPT_KIND_TOAST kinds.
+ */
+static const relopt_parse_elt stdRdOptionsTab[] = {
+       {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
+       {"autovacuum_enabled", RELOPT_TYPE_TERNARY,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
+       {"autovacuum_parallel_workers", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
autovacuum_parallel_workers)},
+       {"autovacuum_vacuum_threshold", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_threshold)},
+       {"autovacuum_vacuum_max_threshold", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_max_threshold)},
+       {"autovacuum_vacuum_insert_threshold", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_ins_threshold)},
+       {"autovacuum_analyze_threshold", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
analyze_threshold)},
+       {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_cost_limit)},
+       {"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
freeze_min_age)},
+       {"autovacuum_freeze_max_age", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
freeze_max_age)},
+       {"autovacuum_freeze_table_age", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
freeze_table_age)},
+       {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
multixact_freeze_min_age)},
+       {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
multixact_freeze_max_age)},
+       {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
multixact_freeze_table_age)},
+       {"log_autovacuum_min_duration", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
log_vacuum_min_duration)},
+       {"log_autoanalyze_min_duration", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
log_analyze_min_duration)},
+       {"toast_tuple_target", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, toast_tuple_target)},
+       {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_REAL,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_cost_delay)},
+       {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_scale_factor)},
+       {"autovacuum_vacuum_insert_scale_factor", RELOPT_TYPE_REAL,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_ins_scale_factor)},
+       {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
+       offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
analyze_scale_factor)},
+       {"user_catalog_table", RELOPT_TYPE_BOOL,
+       offsetof(StdRdOptions, user_catalog_table)},
+       {"parallel_workers", RELOPT_TYPE_INT,
+       offsetof(StdRdOptions, parallel_workers)},
+       {"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+       offsetof(StdRdOptions, vacuum_index_cleanup)},
+       {"vacuum_truncate", RELOPT_TYPE_TERNARY,
+       offsetof(StdRdOptions, vacuum_truncate)},
+       {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
+       offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
+};
+
 /*
  * Option parser for anything that uses StdRdOptions.
  */
 bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
-       static const relopt_parse_elt tab[] = {
-               {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, 
fillfactor)},
-               {"autovacuum_enabled", RELOPT_TYPE_TERNARY,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
enabled)},
-               {"autovacuum_parallel_workers", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
autovacuum_parallel_workers)},
-               {"autovacuum_vacuum_threshold", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_threshold)},
-               {"autovacuum_vacuum_max_threshold", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_max_threshold)},
-               {"autovacuum_vacuum_insert_threshold", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_ins_threshold)},
-               {"autovacuum_analyze_threshold", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
analyze_threshold)},
-               {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_cost_limit)},
-               {"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
freeze_min_age)},
-               {"autovacuum_freeze_max_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
freeze_max_age)},
-               {"autovacuum_freeze_table_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
freeze_table_age)},
-               {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
multixact_freeze_min_age)},
-               {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
multixact_freeze_max_age)},
-               {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
multixact_freeze_table_age)},
-               {"log_autovacuum_min_duration", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
log_vacuum_min_duration)},
-               {"log_autoanalyze_min_duration", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
log_analyze_min_duration)},
-               {"toast_tuple_target", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, toast_tuple_target)},
-               {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_REAL,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_cost_delay)},
-               {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_scale_factor)},
-               {"autovacuum_vacuum_insert_scale_factor", RELOPT_TYPE_REAL,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
vacuum_ins_scale_factor)},
-               {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, 
analyze_scale_factor)},
-               {"user_catalog_table", RELOPT_TYPE_BOOL,
-               offsetof(StdRdOptions, user_catalog_table)},
-               {"parallel_workers", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, parallel_workers)},
-               {"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
-               offsetof(StdRdOptions, vacuum_index_cleanup)},
-               {"vacuum_truncate", RELOPT_TYPE_TERNARY,
-               offsetof(StdRdOptions, vacuum_truncate)},
-               {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
-               offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
-       };
-
        return (bytea *) build_reloptions(reloptions, validate, kind,
                                                                          
sizeof(StdRdOptions),
-                                                                         tab, 
lengthof(tab));
+                                                                         
stdRdOptionsTab,
+                                                                         
lengthof(stdRdOptionsTab));
 }
 
 /*
-- 
2.50.1 (Apple Git-155)

>From a3f9114efaa75412eeaaede9120b280d46ec3a49 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 11 Aug 2026 15:43:15 -0500
Subject: [PATCH v8 7/7] Fix VACUUM and autovacuum handling of TOAST storage
 parameters.

Per the documentation for CREATE TABLE:

    If a table parameter value is set and the equivalent toast.
    parameter is not, the TOAST table will use the table's
    parameter value.

Unfortunately, current reality does not match this description.
Neither VACUUM nor autovacuum consults the main table's
non-autovacuum storage parameters, and autovacuum only consults the
main table's autovacuum-related storage parameters if the TOAST
table lacks any.  One silver lining is that all currently-supported
TOAST storage parameters are related to vacuum, whose code paths
already access the main table's parameters.  This means that our
solution needn't involve more lookups; we just need to propagate
them correctly.

To fix, this commit teaches autovacuum to combine the TOAST storage
parameters with the main table's (with the toast.* ones winning if
both are set), and it teaches VACUUM to send down the main table's
parameters when recursing to a TOAST table.  This doesn't fix
VACUUM against a TOAST table directly (e.g., VACUUM
pg_toast.pg_toast_5432), but that's probably okay because it's not
the main supported way to vacuum a TOAST table (see VACUUM's
PROCESS_MAIN and PROCESS_TOAST options).

An existing shortcoming that this patch only makes worse is that
autovacuum/VACUUM remain oblivious to concurrent storage parameter
changes on the main table.  That is, the main table's parameters
may be captured long before its TOAST table is processed, and a
user may very well have altered the settings in the meantime.
Fixing that would likely require additional pg_class lookups, and
it's not clear if it's worth the trouble.

While this is a bug fix, it's too intrusive for back-patching, but
the issue seems to have gone unnoticed for a very long time,
anyway.
---
 src/backend/access/common/reloptions.c        |  93 ++++++++++
 src/backend/commands/vacuum.c                 |  30 +++-
 src/backend/postmaster/autovacuum.c           | 159 +++++++++++++++---
 src/include/access/reloptions.h               |   2 +
 src/include/commands/vacuum.h                 |   8 +
 .../injection_points/expected/vacuum.out      |  29 ++++
 .../modules/injection_points/sql/vacuum.sql   |  16 ++
 7 files changed, 311 insertions(+), 26 deletions(-)

diff --git a/src/backend/access/common/reloptions.c 
b/src/backend/access/common/reloptions.c
index 4548eb02676..60f1dc7ddd5 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -2114,6 +2114,99 @@ default_reloptions(Datum reloptions, bool validate, 
relopt_kind kind)
                                                                          
lengthof(stdRdOptionsTab));
 }
 
+/*
+ * find_reloption
+ *             Look up a reloption of the given kind by name.
+ *
+ * Returns NULL if no such option can be set on relations of that kind.  Note
+ * that names are unique only within a kind; "fillfactor", for example, is
+ * declared separately for heaps and for each index access method.
+ */
+static relopt_gen *
+find_reloption(const char *name, relopt_kind kind)
+{
+       if (need_initialization)
+               initialize_reloptions();
+
+       for (int i = 0; relOpts[i]; i++)
+       {
+               if ((relOpts[i]->kinds & kind) != 0 &&
+                       strcmp(relOpts[i]->name, name) == 0)
+                       return relOpts[i];
+       }
+
+       return NULL;
+}
+
+/*
+ * merge_toast_reloptions
+ *             Fill in a TOAST table's unset options from its main table's.
+ *
+ * Any option that may be set on a TOAST table but was not is taken from
+ * main_opts.  Either argument may be NULL; if both are, NULL is returned.
+ * Otherwise, the options to use are returned.
+ *
+ * An option counts as unset while it still holds the default declared for it
+ * above, which works because nothing a TOAST table accepts has a default the
+ * user could also set (see assert_toast_defaults_unsettable()).
+ *
+ * NB: This destructively modifies toast_opts, and what it returns may be
+ * either argument, so the caller must know which of the two it owns.
+ */
+StdRdOptions *
+merge_toast_reloptions(StdRdOptions *toast_opts, StdRdOptions *main_opts)
+{
+       if (toast_opts == NULL)
+               return main_opts;
+       if (main_opts == NULL)
+               return toast_opts;
+
+       for (int i = 0; i < lengthof(stdRdOptionsTab); i++)
+       {
+               const relopt_parse_elt *elem = &stdRdOptionsTab[i];
+               relopt_gen *gen;
+               char       *toast_val;
+               char       *main_val;
+
+               /* Skip anything that cannot be set on a TOAST table. */
+               gen = find_reloption(elem->optname, RELOPT_KIND_TOAST);
+               if (gen == NULL)
+                       continue;
+
+               toast_val = (char *) toast_opts + elem->offset;
+               main_val = (char *) main_opts + elem->offset;
+
+               switch (gen->type)
+               {
+                       case RELOPT_TYPE_TERNARY:
+                               if (*(pg_ternary *) toast_val == 
PG_TERNARY_UNSET)
+                                       *(pg_ternary *) toast_val = 
*(pg_ternary *) main_val;
+                               break;
+
+                       case RELOPT_TYPE_INT:
+                               if (*(int *) toast_val == ((relopt_int *) 
gen)->default_val)
+                                       *(int *) toast_val = *(int *) main_val;
+                               break;
+
+                       case RELOPT_TYPE_REAL:
+                               if (*(double *) toast_val == ((relopt_real *) 
gen)->default_val)
+                                       *(double *) toast_val = *(double *) 
main_val;
+                               break;
+
+                       case RELOPT_TYPE_ENUM:
+                               if (*(int *) toast_val == ((relopt_enum *) 
gen)->default_val)
+                                       *(int *) toast_val = *(int *) main_val;
+                               break;
+
+                       default:
+                               elog(ERROR, "reloption \"%s\" has a type a 
TOAST table cannot inherit",
+                                        elem->optname);
+               }
+       }
+
+       return toast_opts;
+}
+
 /*
  * build_reloptions
  *
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 52116c02b59..3af66006586 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -187,6 +187,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool 
isTopLevel)
 
        /* Will be set later if we recurse to a TOAST table. */
        params.toast_parent = InvalidOid;
+       params.main_index_cleanup = VACOPTVALUE_UNSPECIFIED;
+       params.main_truncate = VACOPTVALUE_UNSPECIFIED;
+       params.main_max_eager_freeze_failure_rate = -1.0;
 
        /*
         * Set this to an invalid value so it is clear whether or not a
@@ -2203,7 +2206,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams 
params,
 
        /*
         * Set index_cleanup option based on index_cleanup reloption if it 
wasn't
-        * specified in VACUUM command, or when running in an autovacuum worker
+        * specified in VACUUM command, or when running in an autovacuum 
worker. A
+        * TOAST table with no setting of its own inherits the main table's 
value.
         */
        if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
        {
@@ -2227,11 +2231,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams 
params,
                                params.index_cleanup = VACOPTVALUE_AUTO;
                                break;
                        case STDRD_OPTION_VACUUM_INDEX_CLEANUP_NOT_SET:
-                               params.index_cleanup = VACOPTVALUE_AUTO;
+                               if (params.main_index_cleanup != 
VACOPTVALUE_UNSPECIFIED)
+                                       params.index_cleanup = 
params.main_index_cleanup;
+                               else
+                                       params.index_cleanup = VACOPTVALUE_AUTO;
                                break;
                }
        }
 
+       toast_vacuum_params.main_index_cleanup = params.index_cleanup;
+
 #ifdef USE_INJECTION_POINTS
        if (params.index_cleanup == VACOPTVALUE_AUTO)
                INJECTION_POINT("vacuum-index-cleanup-auto", NULL);
@@ -2243,16 +2252,25 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams 
params,
 
        /*
         * Check if the vacuum_max_eager_freeze_failure_rate table storage
-        * parameter was specified. This overrides the GUC value.
+        * parameter was specified. This overrides the GUC value.  A TOAST table
+        * with no setting of its own inherits the main table's value.
         */
        if (rel->rd_options != NULL &&
                ((StdRdOptions *) 
rel->rd_options)->vacuum_max_eager_freeze_failure_rate >= 0)
                params.max_eager_freeze_failure_rate =
                        ((StdRdOptions *) 
rel->rd_options)->vacuum_max_eager_freeze_failure_rate;
+       else if (params.main_max_eager_freeze_failure_rate >= 0.0)
+               params.max_eager_freeze_failure_rate =
+                       params.main_max_eager_freeze_failure_rate;
+
+       toast_vacuum_params.main_max_eager_freeze_failure_rate =
+               params.max_eager_freeze_failure_rate;
 
        /*
         * Set truncate option based on truncate reloption or GUC if it wasn't
-        * specified in VACUUM command, or when running in an autovacuum worker
+        * specified in VACUUM command, or when running in an autovacuum 
worker. A
+        * TOAST table with no setting of its own inherits the main table's 
value
+        * before falling back to the GUC.
         */
        if (params.truncate == VACOPTVALUE_UNSPECIFIED)
        {
@@ -2265,12 +2283,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams 
params,
                        else
                                params.truncate = VACOPTVALUE_DISABLED;
                }
+               else if (params.main_truncate != VACOPTVALUE_UNSPECIFIED)
+                       params.truncate = params.main_truncate;
                else if (vacuum_truncate)
                        params.truncate = VACOPTVALUE_ENABLED;
                else
                        params.truncate = VACOPTVALUE_DISABLED;
        }
 
+       toast_vacuum_params.main_truncate = params.truncate;
+
 #ifdef USE_INJECTION_POINTS
        if (params.truncate == VACOPTVALUE_AUTO)
                INJECTION_POINT("vacuum-truncate-auto", NULL);
diff --git a/src/backend/postmaster/autovacuum.c 
b/src/backend/postmaster/autovacuum.c
index a99f7108636..881e07ecf9e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2015,9 +2015,9 @@ do_autovacuum(void)
         * We do this in two passes: on the first one we collect the list of 
plain
         * relations and materialized views, and on the second one we collect
         * TOAST tables. The reason for doing the second pass is that during it 
we
-        * want to use the main relation's pg_class.reloptions entry if the 
TOAST
-        * table does not have any, and we cannot obtain it unless we know
-        * beforehand what's the main table OID.
+        * want to fill in any storage parameters that the TOAST table does not
+        * set with the main relation's, and we cannot obtain those values 
unless
+        * we know beforehand what's the main table OID.
         *
         * We need to check TOAST tables separately because in cases with short,
         * wide tables there might be proportionally much more activity in the
@@ -2133,6 +2133,8 @@ do_autovacuum(void)
                bool            doanalyze;
                bool            wraparound;
                AutoVacuumScores scores;
+               av_relation *hentry;
+               bool            found;
 
                /*
                 * We cannot safely process other backends' temp tables, so 
skip 'em.
@@ -2143,21 +2145,19 @@ do_autovacuum(void)
                relid = classForm->oid;
 
                /*
-                * fetch reloptions -- if this toast table does not have them, 
try the
-                * main rel
+                * fetch reloptions -- merge any unset options from the main rel
+                *
+                * Note that this fills in the storage parameters only VACUUM
+                * consults, too.  That does no harm here; whether we process 
the
+                * table depends on the autovacuum parameters alone.
                 */
                relopts = (StdRdOptions *) extractRelOptions(tuple, 
pg_class_desc, NULL);
                if (relopts)
                        free_relopts = true;
-               else
-               {
-                       av_relation *hentry;
-                       bool            found;
 
-                       hentry = hash_search(table_toast_map, &relid, 
HASH_FIND, &found);
-                       if (found)
-                               relopts = &hentry->ar_reloptions;
-               }
+               hentry = hash_search(table_toast_map, &relid, HASH_FIND, 
&found);
+               if (found)
+                       relopts = merge_toast_reloptions(relopts, 
&hentry->ar_reloptions);
 
                relation_needs_vacanalyze(relid, relopts, classForm,
                                                                  
effective_multixact_freeze_max_age,
@@ -2782,6 +2782,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
        autovac_table *tab = NULL;
        bool            wraparound;
        StdRdOptions *relopts;
+       StdRdOptions *main_relopts = NULL;
        bool            free_relopts = false;
        AutoVacuumScores scores;
 
@@ -2792,20 +2793,24 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
        classForm = (Form_pg_class) GETSTRUCT(classTup);
 
        /*
-        * Get the applicable reloptions.  If it is a TOAST table, try to get 
the
-        * main table reloptions if the toast table itself doesn't have.
+        * Get the applicable reloptions.  If it is a TOAST table, merge in the
+        * main table's reloptions where they are unset.
         */
        relopts = (StdRdOptions *) extractRelOptions(classTup, pg_class_desc, 
NULL);
        if (relopts)
                free_relopts = true;
-       else if (classForm->relkind == RELKIND_TOASTVALUE)
+
+       if (classForm->relkind == RELKIND_TOASTVALUE)
        {
                av_relation *hentry;
                bool            found;
 
                hentry = hash_search(table_toast_map, &relid, HASH_FIND, 
&found);
                if (found)
-                       relopts = &hentry->ar_reloptions;
+               {
+                       main_relopts = &hentry->ar_reloptions;
+                       relopts = merge_toast_reloptions(relopts, main_relopts);
+               }
        }
 
        relation_needs_vacanalyze(relid, relopts, classForm,
@@ -2892,6 +2897,49 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
                tab->at_params.log_analyze_min_duration = 
log_analyze_min_duration;
                tab->at_params.toast_parent = InvalidOid;
 
+               /*
+                * For TOAST tables, provide fallbacks for the options that
+                * vacuum_rel() resolves from the relation's own reloptions, 
which do
+                * not have the main table's values merged in.
+                */
+               tab->at_params.main_index_cleanup = VACOPTVALUE_UNSPECIFIED;
+               tab->at_params.main_truncate = VACOPTVALUE_UNSPECIFIED;
+               tab->at_params.main_max_eager_freeze_failure_rate = -1.0;
+
+               if (main_relopts != NULL)
+               {
+                       switch (main_relopts->vacuum_index_cleanup)
+                       {
+                               case STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON:
+                                       tab->at_params.main_index_cleanup = 
VACOPTVALUE_ENABLED;
+                                       break;
+                               case STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF:
+                                       tab->at_params.main_index_cleanup = 
VACOPTVALUE_DISABLED;
+                                       break;
+                               case STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO:
+                                       tab->at_params.main_index_cleanup = 
VACOPTVALUE_AUTO;
+                                       break;
+                               case STDRD_OPTION_VACUUM_INDEX_CLEANUP_NOT_SET:
+                                       break;
+                       }
+
+                       switch (main_relopts->vacuum_truncate)
+                       {
+                               case PG_TERNARY_TRUE:
+                                       tab->at_params.main_truncate = 
VACOPTVALUE_ENABLED;
+                                       break;
+                               case PG_TERNARY_FALSE:
+                                       tab->at_params.main_truncate = 
VACOPTVALUE_DISABLED;
+                                       break;
+                               case PG_TERNARY_UNSET:
+                                       break;
+                       }
+
+                       if (main_relopts->vacuum_max_eager_freeze_failure_rate 
>= 0.0)
+                               
tab->at_params.main_max_eager_freeze_failure_rate =
+                                       
main_relopts->vacuum_max_eager_freeze_failure_rate;
+               }
+
                /* Determine the number of parallel vacuum workers to use */
                tab->at_params.nworkers = 0;
                if (avopts)
@@ -2948,9 +2996,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
  * "dovacuum" and "doanalyze", respectively.  Also return whether the vacuum is
  * being forced because of Xid or multixact wraparound.
  *
- * relopts is a pointer to the StdRdOptions options (either for itself in the
- * case of a plain table, or for either itself or its parent table in the case
- * of a TOAST table), NULL if none.
+ * relopts is a pointer to the StdRdOptions options (the relation's own for a
+ * plain table, or those merged with the main table's for a TOAST table), NULL
+ * if none.
  *
  * A table needs to be vacuumed if the number of dead tuples exceeds a
  * threshold.  This threshold is calculated as
@@ -3607,6 +3655,8 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
        Relation        rel;
        TableScanDesc scan;
        HeapTuple       tup;
+       HTAB       *table_toast_map;
+       HASHCTL         ctl;
        ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 
        InitMaterializedSRF(fcinfo, 0);
@@ -3616,13 +3666,64 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
        recentXid = ReadNextTransactionId();
        recentMulti = ReadNextMultiXactId();
 
-       /* scan pg_class */
+       /* create hash table for toast <-> main relid mapping */
+       ctl.keysize = sizeof(Oid);
+       ctl.entrysize = sizeof(av_relation);
+       ctl.hcxt = CurrentMemoryContext;
+       table_toast_map = hash_create("TOAST to main relid map",
+                                                                 100,
+                                                                 &ctl,
+                                                                 HASH_ELEM | 
HASH_BLOBS | HASH_CONTEXT);
+
        rel = table_open(RelationRelationId, AccessShareLock);
+
+       /*
+        * Do an initial pass over pg_class to collect the main relations'
+        * reloptions, which we need in order to compute their TOAST tables'
+        * effective options below.
+        */
        scan = table_beginscan_catalog(rel, 0, NULL);
        while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
        {
                Form_pg_class form = (Form_pg_class) GETSTRUCT(tup);
                StdRdOptions *relopts;
+               av_relation *hentry;
+               bool            found;
+
+               /* skip ineligible entries */
+               if (form->relkind != RELKIND_RELATION &&
+                       form->relkind != RELKIND_MATVIEW)
+                       continue;
+               if (form->relpersistence == RELPERSISTENCE_TEMP)
+                       continue;
+               if (!OidIsValid(form->reltoastrelid))
+                       continue;
+
+               relopts = (StdRdOptions *) extractRelOptions(tup, 
RelationGetDescr(rel), NULL);
+               if (!relopts)
+                       continue;
+
+               hentry = hash_search(table_toast_map, &form->reltoastrelid,
+                                                        HASH_ENTER, &found);
+               Assert(!found);                 /* rels cannot share a TOAST 
table */
+
+               /* hash_search already filled in the key */
+               memcpy(&hentry->ar_reloptions, relopts, sizeof(StdRdOptions));
+
+               pfree(relopts);
+       }
+       table_endscan(scan);
+
+       /*
+        * Now that we have all parent tables' reloptions, we can generate the
+        * results.
+        */
+       scan = table_beginscan_catalog(rel, 0, NULL);
+       while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+       {
+               Form_pg_class form = (Form_pg_class) GETSTRUCT(tup);
+               StdRdOptions *relopts;
+               bool            free_relopts = false;
                bool            dovacuum;
                bool            doanalyze;
                bool            wraparound;
@@ -3639,12 +3740,25 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
                        continue;
 
                relopts = (StdRdOptions *) extractRelOptions(tup, 
RelationGetDescr(rel), NULL);
+               if (relopts)
+                       free_relopts = true;
+               if (form->relkind == RELKIND_TOASTVALUE)
+               {
+                       av_relation *hentry;
+                       bool            found;
+
+                       hentry = hash_search(table_toast_map, &form->oid,
+                                                                HASH_FIND, 
&found);
+                       if (found)
+                               relopts = merge_toast_reloptions(relopts, 
&hentry->ar_reloptions);
+               }
+
                relation_needs_vacanalyze(form->oid, relopts, form,
                                                                  
effective_multixact_freeze_max_age,
                                                                  LOG_NEVER,
                                                                  &dovacuum, 
&doanalyze, &wraparound,
                                                                  &scores);
-               if (relopts)
+               if (free_relopts)
                        pfree(relopts);
 
                vals[0] = ObjectIdGetDatum(form->oid);
@@ -3662,6 +3776,7 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
        }
        table_endscan(scan);
        table_close(rel, AccessShareLock);
+       hash_destroy(table_toast_map);
 
        return (Datum) 0;
 }
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index e8cb7f7a627..6c599382f02 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -247,6 +247,8 @@ extern void *build_local_reloptions(local_relopts *relopts, 
Datum options,
 
 extern bytea *default_reloptions(Datum reloptions, bool validate,
                                                                 relopt_kind 
kind);
+extern struct StdRdOptions *merge_toast_reloptions(struct StdRdOptions 
*toast_opts,
+                                                                               
                   struct StdRdOptions *main_opts);
 extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate);
 extern bytea *view_reloptions(Datum reloptions, bool validate);
 extern bytea *partitioned_table_reloptions(Datum reloptions, bool validate);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index e62f23748dc..fdfceb38396 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -248,6 +248,14 @@ typedef struct VacuumParams
         * disabled.
         */
        int                     nworkers;
+
+       /*
+        * Main table fallback values for TOAST tables to inherit when they have
+        * no setting of their own.
+        */
+       VacOptValue main_index_cleanup;
+       VacOptValue main_truncate;
+       double          main_max_eager_freeze_failure_rate;
 } VacuumParams;
 
 /*
diff --git a/src/test/modules/injection_points/expected/vacuum.out 
b/src/test/modules/injection_points/expected/vacuum.out
index 58df59fa927..1f9acc73594 100644
--- a/src/test/modules/injection_points/expected/vacuum.out
+++ b/src/test/modules/injection_points/expected/vacuum.out
@@ -79,9 +79,38 @@ NOTICE:  notice triggered for injection point 
vacuum-truncate-enabled
 NOTICE:  notice triggered for injection point vacuum-index-cleanup-auto
 NOTICE:  notice triggered for injection point vacuum-truncate-enabled
 RESET vacuum_truncate;
+-- TOAST table inherits main table's resolved values
+CREATE TABLE vac_tab_toast_inherit(i int, j text STORAGE EXTERNAL) WITH
+  (autovacuum_enabled=false,
+   vacuum_index_cleanup=false,
+   autovacuum_vacuum_insert_threshold=1,
+   autovacuum_vacuum_insert_scale_factor=0,
+   vacuum_truncate=false, toast.vacuum_truncate=true);
+VACUUM vac_tab_toast_inherit;
+NOTICE:  notice triggered for injection point vacuum-index-cleanup-disabled
+NOTICE:  notice triggered for injection point vacuum-truncate-disabled
+NOTICE:  notice triggered for injection point vacuum-index-cleanup-disabled
+NOTICE:  notice triggered for injection point vacuum-truncate-enabled
+INSERT INTO vac_tab_toast_inherit
+  VALUES (1, repeat('a', 10000)), (2, repeat('b', 10000));
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT s.vacuum_insert_score > 1 AS over
+  FROM pg_class c, pg_stat_autovacuum_scores s
+  WHERE s.relid = c.reltoastrelid AND c.relname = 'vac_tab_toast_inherit';
+ over 
+------
+ t
+(1 row)
+
 DROP TABLE vac_tab_auto;
 DROP TABLE vac_tab_on_toast_off;
 DROP TABLE vac_tab_off_toast_on;
+DROP TABLE vac_tab_toast_inherit;
 -- Cleanup
 SELECT injection_points_detach('vacuum-index-cleanup-auto');
  injection_points_detach 
diff --git a/src/test/modules/injection_points/sql/vacuum.sql 
b/src/test/modules/injection_points/sql/vacuum.sql
index 23760dd0f38..085f64337ad 100644
--- a/src/test/modules/injection_points/sql/vacuum.sql
+++ b/src/test/modules/injection_points/sql/vacuum.sql
@@ -33,9 +33,25 @@ SET vacuum_truncate = true;
 VACUUM vac_tab_auto;
 RESET vacuum_truncate;
 
+-- TOAST table inherits main table's resolved values
+CREATE TABLE vac_tab_toast_inherit(i int, j text STORAGE EXTERNAL) WITH
+  (autovacuum_enabled=false,
+   vacuum_index_cleanup=false,
+   autovacuum_vacuum_insert_threshold=1,
+   autovacuum_vacuum_insert_scale_factor=0,
+   vacuum_truncate=false, toast.vacuum_truncate=true);
+VACUUM vac_tab_toast_inherit;
+INSERT INTO vac_tab_toast_inherit
+  VALUES (1, repeat('a', 10000)), (2, repeat('b', 10000));
+SELECT pg_stat_force_next_flush();
+SELECT s.vacuum_insert_score > 1 AS over
+  FROM pg_class c, pg_stat_autovacuum_scores s
+  WHERE s.relid = c.reltoastrelid AND c.relname = 'vac_tab_toast_inherit';
+
 DROP TABLE vac_tab_auto;
 DROP TABLE vac_tab_on_toast_off;
 DROP TABLE vac_tab_off_toast_on;
+DROP TABLE vac_tab_toast_inherit;
 
 -- Cleanup
 SELECT injection_points_detach('vacuum-index-cleanup-auto');
-- 
2.50.1 (Apple Git-155)

Reply via email to