On Thu, Aug 06, 2026 at 04:38:28PM -0500, Nathan Bossart wrote: > Thanks for looking. Here is a new patch set with some small fixes.
Sorry for the noise. I found some other small bugs and found another small cleanup opportunity. -- nathan
>From 5eac9ea9e87ad8cc79ad61b78d925231e507c31d Mon Sep 17 00:00:00 2001 From: Nathan Bossart <[email protected]> Date: Mon, 8 Jun 2026 14:35:34 -0500 Subject: [PATCH v7 1/5] 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 9364278cb284584cb25e205fe57330fe84347e45 Mon Sep 17 00:00:00 2001 From: Nathan Bossart <[email protected]> Date: Mon, 8 Jun 2026 14:50:36 -0500 Subject: [PATCH v7 2/5] 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 7c1b94407a9..1cd1368984e 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -2897,7 +2897,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 ec0fc6e0fe47bc54c00f314a5d4e1f01e8d7d110 Mon Sep 17 00:00:00 2001 From: Nathan Bossart <[email protected]> Date: Mon, 8 Jun 2026 15:27:58 -0500 Subject: [PATCH v7 3/5] 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 f86658f7f7c84c2e7478999e9d52b4ec96c5ae7d Mon Sep 17 00:00:00 2001 From: Nathan Bossart <[email protected]> Date: Fri, 7 Aug 2026 10:55:34 -0500 Subject: [PATCH v7 4/5] 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 327d421f505dc2776f76a6287708fe9d73626c8b Mon Sep 17 00:00:00 2001 From: Nathan Bossart <[email protected]> Date: Thu, 6 Aug 2026 15:19:54 -0500 Subject: [PATCH v7 5/5] 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 | 2 +- src/backend/commands/vacuum.c | 36 ++- src/backend/postmaster/autovacuum.c | 270 ++++++++++++++++-- src/include/commands/vacuum.h | 8 + src/include/utils/rel.h | 3 + .../injection_points/expected/vacuum.out | 11 + .../modules/injection_points/sql/vacuum.sql | 8 + 7 files changed, 310 insertions(+), 28 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 58eb72e2339..94e9152cbab 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 }, { { diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 52116c02b59..6a8df38ca13 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) { @@ -2219,15 +2223,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { case STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON: params.index_cleanup = VACOPTVALUE_ENABLED; + toast_vacuum_params.main_index_cleanup = VACOPTVALUE_ENABLED; break; case STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF: params.index_cleanup = VACOPTVALUE_DISABLED; + toast_vacuum_params.main_index_cleanup = VACOPTVALUE_DISABLED; break; case STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO: params.index_cleanup = VACOPTVALUE_AUTO; + toast_vacuum_params.main_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; } } @@ -2243,16 +2253,26 @@ 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; + toast_vacuum_params.main_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; /* * 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) { @@ -2261,10 +2281,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, if (opts && opts->vacuum_truncate != PG_TERNARY_UNSET) { if (opts->vacuum_truncate == PG_TERNARY_TRUE) + { params.truncate = VACOPTVALUE_ENABLED; + toast_vacuum_params.main_truncate = VACOPTVALUE_ENABLED; + } else + { params.truncate = VACOPTVALUE_DISABLED; + toast_vacuum_params.main_truncate = VACOPTVALUE_DISABLED; + } } + else if (params.main_truncate != VACOPTVALUE_UNSPECIFIED) + params.truncate = params.main_truncate; else if (vacuum_truncate) params.truncate = VACOPTVALUE_ENABLED; else diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 32efe967890..02c279d419b 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -392,6 +392,8 @@ static void autovac_report_workitem(AutoVacuumWorkItem *workitem, static void avl_sigusr2_handler(SIGNAL_ARGS); static bool av_worker_available(void); static void check_av_worker_gucs(void); +static StdRdOptions *merge_autovac_opts(StdRdOptions *toast_opts, + StdRdOptions *main_opts); @@ -2015,9 +2017,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 +2135,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 +2147,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 we don't bother merging the non-autovacuum relopts here + * because they do not impact our choice of whether to process the + * table. */ 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_autovac_opts(relopts, &hentry->ar_reloptions); relation_needs_vacanalyze(relid, relopts, classForm, effective_multixact_freeze_max_age, @@ -2782,6 +2784,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 +2795,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_autovac_opts(relopts, main_relopts); + } } relation_needs_vacanalyze(relid, relopts, classForm, @@ -2832,7 +2839,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; @@ -2892,6 +2899,48 @@ 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 options that are not part + * of AutoVacOpts (and thus are not handled by merge_autovac_opts()). + */ + 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 +2997,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 +3656,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 +3667,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 +3741,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_autovac_opts(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 +3777,115 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS) } table_endscan(scan); table_close(rel, AccessShareLock); + hash_destroy(table_toast_map); return (Datum) 0; } + +/* + * Combine a TOAST table's relevant autovacuum options with the main table's. + * Any option the TOAST table leaves unset is taken from the main table's + * options. Either argument may be NULL. If both are NULL, NULL is returned. + * Otherwise, the options to use are returned. + * + * The arrays below cover only the options that can be set on a TOAST table, + * grouped by the value that means "unset". Both of those facts come from + * reloptions.c, so they must be kept in sync with the entries here. + * + * NB: This function destructively modifies toast_opts! + */ +static StdRdOptions * +merge_autovac_opts(StdRdOptions *toast_opts, StdRdOptions *main_opts) +{ + /* ternary fields */ + static const int ternary_offsets[] = { + offsetof(AutoVacOpts, enabled), + }; + + /* integer fields whose unset sentinel is -1 */ + static const int int_offsets_1[] = { + offsetof(AutoVacOpts, vacuum_threshold), + offsetof(AutoVacOpts, vacuum_cost_limit), + offsetof(AutoVacOpts, freeze_min_age), + offsetof(AutoVacOpts, freeze_max_age), + offsetof(AutoVacOpts, freeze_table_age), + offsetof(AutoVacOpts, multixact_freeze_min_age), + offsetof(AutoVacOpts, multixact_freeze_max_age), + offsetof(AutoVacOpts, multixact_freeze_table_age), + }; + + /* integer fields whose unset sentinel is -2 */ + static const int int_offsets_2[] = { + offsetof(AutoVacOpts, vacuum_max_threshold), + offsetof(AutoVacOpts, vacuum_ins_threshold), + offsetof(AutoVacOpts, log_vacuum_min_duration), + }; + + /* float fields */ + static const int float_offsets[] = { + offsetof(AutoVacOpts, vacuum_cost_delay), + offsetof(AutoVacOpts, vacuum_scale_factor), + offsetof(AutoVacOpts, vacuum_ins_scale_factor), + }; + + AutoVacOpts *toast_avopts; + AutoVacOpts *main_avopts; + + if (toast_opts == NULL) + return main_opts; + if (main_opts == NULL) + return toast_opts; + + toast_avopts = &toast_opts->autovacuum; + main_avopts = &main_opts->autovacuum; + + for (int i = 0; i < lengthof(ternary_offsets); i++) + { + pg_ternary *toast_opt; + pg_ternary *main_opt; + + toast_opt = (pg_ternary *) ((char *) toast_avopts + ternary_offsets[i]); + main_opt = (pg_ternary *) ((char *) main_avopts + ternary_offsets[i]); + + if (*toast_opt == PG_TERNARY_UNSET) + *toast_opt = *main_opt; + } + + for (int i = 0; i < lengthof(int_offsets_1); i++) + { + int *toast_opt; + int *main_opt; + + toast_opt = (int *) ((char *) toast_avopts + int_offsets_1[i]); + main_opt = (int *) ((char *) main_avopts + int_offsets_1[i]); + + if (*toast_opt == -1) + *toast_opt = *main_opt; + } + + for (int i = 0; i < lengthof(int_offsets_2); i++) + { + int *toast_opt; + int *main_opt; + + toast_opt = (int *) ((char *) toast_avopts + int_offsets_2[i]); + main_opt = (int *) ((char *) main_avopts + int_offsets_2[i]); + + if (*toast_opt == -2) + *toast_opt = *main_opt; + } + + for (int i = 0; i < lengthof(float_offsets); i++) + { + double *toast_opt; + double *main_opt; + + toast_opt = (double *) ((char *) toast_avopts + float_offsets[i]); + main_opt = (double *) ((char *) main_avopts + float_offsets[i]); + + if (*toast_opt == -1.0) + *toast_opt = *main_opt; + } + + return toast_opts; +} 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/include/utils/rel.h b/src/include/utils/rel.h index 01b6e1a872c..4750ea8d864 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -306,6 +306,9 @@ typedef struct ForeignKeyCacheInfo * RelationGetFillFactor() and RelationGetTargetPageFreeSpace() can only * be applied to relations that use this format or a superset for * private options data. + * + * NB: When adding a new member, be sure to update merge_autovac_opts() and/or + * table_recheck_autovac() as necessary! */ /* autovacuum-related reloptions. */ typedef struct AutoVacOpts diff --git a/src/test/modules/injection_points/expected/vacuum.out b/src/test/modules/injection_points/expected/vacuum.out index 58df59fa927..0f0790a8584 100644 --- a/src/test/modules/injection_points/expected/vacuum.out +++ b/src/test/modules/injection_points/expected/vacuum.out @@ -79,6 +79,17 @@ 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) WITH + (autovacuum_enabled=false, + vacuum_index_cleanup=false, + 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 +DROP TABLE vac_tab_toast_inherit; DROP TABLE vac_tab_auto; DROP TABLE vac_tab_on_toast_off; DROP TABLE vac_tab_off_toast_on; diff --git a/src/test/modules/injection_points/sql/vacuum.sql b/src/test/modules/injection_points/sql/vacuum.sql index 23760dd0f38..11a371e4ff7 100644 --- a/src/test/modules/injection_points/sql/vacuum.sql +++ b/src/test/modules/injection_points/sql/vacuum.sql @@ -33,6 +33,14 @@ 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) WITH + (autovacuum_enabled=false, + vacuum_index_cleanup=false, + vacuum_truncate=false, toast.vacuum_truncate=true); +VACUUM vac_tab_toast_inherit; +DROP TABLE vac_tab_toast_inherit; + DROP TABLE vac_tab_auto; DROP TABLE vac_tab_on_toast_off; DROP TABLE vac_tab_off_toast_on; -- 2.50.1 (Apple Git-155)
