From c0327f11b5b4e1ace51e7d411aef87a176df72c5 Mon Sep 17 00:00:00 2001 From: Nathan Bossart 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)