agora inbox for pgsql-hackers@postgresql.org  
help / 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