agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. 2096+ messages / 1 participants [nested] [flat]
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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 fab8294e732..a0716ff7819 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 e0fc551d12a..77557fa4bbf 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2838,7 +2838,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.55.0 --WFulFp79iVB4kETe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v13-0003-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --WiF/3tUCR6j/q9wa Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v10-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --IZet2WR372zTYuyO Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v11-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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.55.0 --gzHy1cBrLyG9oicL Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v12-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --QnbX+ZG8KQOMO1rs Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v8-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
* [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. @ 2026-08-10 20:55 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 2096+ messages in thread From: Nathan Bossart @ 2026-08-10 20:55 UTC (permalink / raw) 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) --0oaK7sfrjhxPo9Xw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v9-0006-Move-the-StdRdOptions-parse-table-to-file-scope.patch ^ permalink raw reply [nested|flat] 2096+ messages in thread
end of thread, other threads:[~2026-08-10 20:55 UTC | newest] Thread overview: 2096+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v12 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v13 2/5] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v11 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v10 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v8 5/7] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org> 2026-08-10 20:55 [PATCH v9 5/8] Give TOAST storage parameters unsettable defaults. Nathan Bossart <nathan@postgresql.org>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox